java.lang.Thread
, yang memiliki semua metode untuk membuat dan menjalankan thread. Metode paling penting adalah run()
, yang bisa kita beban-lebihkan untuk melakukan tugas yang kita butuhkan. Atau dengan kata lain run()
adalah metode yang akan dijalankan bersamaan dengan thread lain.Contoh berikut membuat 5 thread, masing-masing memiliki nomor identifikasi unik yang dibuat dengan menggunakan variabel statik. Metode
run()
dibebanlebihkan untuk menghitung mundur hingga hitungMundur
bernilai nol. Setelah metode run()
selesai dijalankan, thread akan mati secara otomatis.(Contoh-contoh pada bagian ini bisa diunduh untuk diimport ke dalam Eclipse. Lihat akhir halaman ini untuk tautannya)
package com.lyracc.threaddasar1; public class ThreadDasar extends Thread { private int hitungMundur = 5; private static int jumlahThread = 0; public ThreadDasar() { super("Thread ke-" + ++jumlahThread); start(); } public void run() { while (true) { System.out.println( getName() + " : " + hitungMundur ); if (--hitungMundur == 0) return; } } /** * @param args */ public static void main(String[] args) { for(int i = 0; i < 5; i++) new ThreadDasar(); } }
run()
melakukan penghitungan mundur, yaitu dengan menggunakan metode getName()
.Metode
run()
pada thread biasanya memiliki perulangan internal yang akan terus
menerus dipanggil hingga tidak lagi digunakan. Kita harus membuat suatu
kondisi sehingga bisa keluar dari perulangan tersebut (misalnya pada
contoh di atas, perulangan akan selesai jika hitungMundur
bernilai 0). Seringkali, run()
dijalankan di dalam perulangan yang tak pernah berhenti (kita akan
lihat nanti bagaimana menghentikan suatu thread dengan aman).Pada metode
main()
, thread dibuat beberapa kali kemudian dijalankan. Metode start()
pada kelas Thread
digunakan untuk melakukan tugas tertentu sebelum metode run()
dijalankan. Jadi, langkah-langkahnya adalah : konstruktor dipanggil untuk membuat objek, kemudian memanggil start()
untuk melakukan konfigurasi thread, dan kemudian metode run()
dijalankan. Jika kita tidak memanggil start()
maka metode run()
tidak akan pernah dijalankan.Keluaran dari program ini akan berbeda setiap kali dijalankan, karena penjadwalan thread tidak dapat ditentukan dengan pasti (non-deterministik). Bahkan, kita bisa melihat perbedaan yang sangat jelas ketika kita menggunakan versi JDK yang berbeda. Misalnya, JDK lama tidak melakukan pembagian waktu lebih cepat, artinya, 1 thread mungkin bisa melakukan tugasnya dengan cepat hingga selesai sebelum thread lain dijalankan. Pada JDK lain kita akan melihat program akan mencetak 5 untuk seluruh thread hingga 1 untuk seluruh thread. Artinya pembagian waktunya lebih baik, karena setiap thread memiliki kesempatan yang sama untuk menjalankan program. Karenanya, untuk membuat suatu program multi-threading, kita tidak boleh terpaku pada keluaran suatu kompiler. Program kita harus dibuat seaman mungkin.
Ketika objek
Thread
dibuat pada metode main()
,
kita lihat bahwa kita tidak menyimpan referensi ke objek tersebut. Pada
objek biasa, tentunya objek ini akan langsung ditangkap oleh pemulung
memori karena objek ini tidak direferensikan di manapun. Akan tetapi
pada thread, objek hanya bisa diambil oleh pemulung memori jika metode run()
selesai dijalankan. Pada contoh di atas, program masih bisa berjalan seperti biasa, dan objek Thread
akan diberikan kepada pemulung memori setelah mencetak angka 1.Yielding (menghasilkan)
Jika kita tahu bahwa kita telah mendapatkan hasil yang kita inginkan pada metode
run()
,
kita bisa memberi tahu penjadwal thread bahwa kita telah selesai dan
memberi jalan kepada thread lain untuk mendapatkan kesempatan pada CPU.
Akan tetapi ini hanya sebagai petunjuk, yang artinya belum tentu
dijalankan oleh penjadwal thread.Misalnya pada contoh di atas, kita bisa mengganti isi metode
run()
denganpublic void run() { while (true) { System.out.println( getName() + " : " + hitungMundur ); if (--hitungMundur == 0) return; yield(); } }
Tidur (sleeping)
Cara lain untuk mengatur perilaku thread kita adalah dengan memanggil
sleep
untuk menunda eksekusi thread selama waktu tertentu (dalam mili detik). Misalnya pada kode berikut, kita ubah metode run()
menjadi seperti :public void run() { while (true) { System.out.println( getName() + " : " + hitungMundur ); if (--hitungMundur == 0) return; try { sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } } }
sleep()
, metode ini harus diletakkan di dalam blok try
karena sleep()
bisa melemparkan pengecualian, yaitu jika tidurnya diganggu sebelum
waktunya selesai. Hal ini terhadi misalnya apabila thread lain yang
memiliki referensi ke thread ini memanggil interrupt()
pada thread ini. Pada contoh di atas, kita lemparkan lagi pengecualian yang terjadi dengan pengecualian lain bertipe RuntimeException
,
karena kita tidak tahu bagaimana pengecualian ini harus ditangani, dan
membiarkan metode yang memanggilnya menangkap pengecualian baru ini.Metode
sleep()
tidak digunakan untuk mengatur bagaimana thread akan berjalan menurut
urutan tertentu. Metode ini hanya menghentikan eksekusi suatu thread
sementara. Yang dijamin adalah bahwa thread akan tidur selama paling
sedikit 100 mili detik (atau mungkin sedikit lebih lama hingga thread
jalan kembali). Urutan thread diatur oleh penjadwal thread yang memiliki
mekanisme sendiri tergantung dari keadaan thread lain atau bahkan
aplikasi lain di luar Java, oleh karena itu sifatnya disebut non-deterministik.Jika kita harus mengatur thread mana dahulu yang harus dijalankan, cara terbaik mungkin tidak menggunakan thread sama sekali, atau mendesain agar suatu thread memanggil thread lain dengan suatu urutan tertentu. Tentunya cara terakhir lebih rumit dari yang dibayangkan.
Prioritas
Prioritas suatu thread digunakan untuk memberi tahu penjadwal thread tentang prioritas thread tersebut. Tetap saja urutannya tidak bisa ditentukan karena sifatnya yang non-deterministik. Jika ada beberapa thread yang sedang diblok dan menunggu giliran untuk dijalankan, penjadwal thread akan cenderung menjalankan thread dengan prioritas tertinggi terlebih dahulu. Akan tetapi, tidak berarti thread dengan prioritas rendah tidak akan pernah dijalankan, hanya lebih jarang dijalankan ketimbang thread dengan prioritas tinggi.
Perhatikan contoh berikut :
package com.lyracc.prioritasthread; public class PrioritasThread extends Thread { private int hitungMundur = 5; private volatile double d = 0; // No optimization public PrioritasThread(int prioritas) { setPriority(prioritas); start(); } public void run() { while (true) { for(int i = 1; i < 100000; i++) d = d + (Math.PI + Math.E) / (double)i; System.out.println(this.toString() + " : " + hitungMundur); if (--hitungMundur == 0) return; } } /** * @param args */ public static void main(String[] args) { new PrioritasThread(Thread.MAX_PRIORITY); for(int i = 0; i < 5; i++) new PrioritasThread(Thread.MIN_PRIORITY); } }
main()
kita buat 6 thread, yang pertama dengan prioritas maximum, dan yang
lain dengan prioritas minimum. Perhatikan keluarannya, bagaimana thread
pertama dijalankan lebih dulu sedangkan thread-thread lain berjalan
seperti biasa dalam kondisi acak karena memiliki prioritas yang sama.Di dalam metode
run()
kita lakukan perhitungan matematika selama 100.000 kali. Tentunya ini
perhitungan yang memakan waktu sehingga setiap thread harus menunggu
giliran di saat thread lain sedang dijalankan. Tanpa perhitungan ini,
thread akan dilaksanakan sangat cepat dan kita tidak bisa melihat efek
dari prioritas thread.Prioritas suatu thread bisa kita set kapan saja (tidak harus pada konstruktor) dengan metode
setPriority(int prioritas)
dan kita bisa membaca prioritas suatu thread dengan menggunakan metode getPriority()
.Meskipun JDK memiliki 10 tingkat prioritas, akan tetapi sistem operasi memiliki tingkat prioritas yang berbeda-beda. Windows misalnya memiliki 7 tingkat dan Solaris memiliki 231 tingkat prioritas. Yang lebih pasti adalah menggunakan konstanta
MAX_PRIORITY
, NORM_PRIORITY
, dan MIN_PRIORITY
pada kelas thread.Thread Daemon
Thread daemon adalah thread yang bekerja di belakang layar yang memberikan layanan umum kepada thread-thread lain selama program berjalan, akan tetapi thread ini bukan bagian penting dari suatu program. Artinya ketika semua thread yang bukan daemon selesai dijalankan, program akan berhenti, dan jika masih ada thread non-daemon yang masih dieksekusi, program tidak akan berhenti.
Perhatikan contoh program berikut ini.
package com.lyracc.threaddaemon; public class ThreadDaemon extends Thread { public ThreadDaemon() { setDaemon(true); // Harus dipanggil sebelum start start(); } public void run() { while (true) { try { sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(this); } } /** * @param args */ public static void main(String[] args) { for (int i = 0; i < 5; i++) new ThreadDaemon(); } }
setDaemon()
sebelum metode start()
dipanggil. Pada metode run()
,
thread diperintahkan untuk tidur selama 100 mili detik. Ketika semua
thread dimulai, program langsung berhenti sebelum thread bisa mencetak
dirinya. Ini karena semua thread kecuali main()
adalah thread daemon. Hanya thread non-daemon saja yang bisa mencegah program untuk terus berjalan.Untuk mengetahui suatu thread adalah thread daemon atau bukan, kita bisa menggunakan perintah
isDaemon()
. Suatu thread daemon akan membuat thread yang juga merupakan thread daemon. Menggabungkan thread
Perintah
join()
bisa digunakan pada thread lain untuk menunda eksekusi hingga thread lain tersebut selesai dijalankan. Misalnya, jika thread a
memanggil t.join()
pada thread t
, maka eksekusi thread a akan terhenti sementara hingga thread t selesai dijalankan (atau ketika t.isAlive()
bernilai false
).Kita bisa juga memanggil
join()
dengan argumen waktu (baik dalam mili detik, ataupun milidetik dan
nanodetik), yaitu jika thread target tidak selesai dalam kurun waktu
tersebut, eksekusi pada thread induk akan kembali dilakukan.Panggilan
join()
bisa dibatalkan dengan memanggil interrupt()
pada thread induk, sehingga klausa try ... catch
diperlukan pada metode join()
.Mari kita lihat contoh berikut ini.
package com.lyracc.joindemo; class ThreadPemalas extends Thread { private int waktu; public ThreadPemalas(String namaThread, int waktuTidur) { super(namaThread); waktu = waktuTidur; start(); } public void run() { try { sleep(waktu); } catch (InterruptedException e) { System.out.println(getName() + " dibangunkan. " + "isInterrupted(): " + isInterrupted()); return; } System.out.println(getName() + " sudah bangun."); } } class ThreadPenggabung extends Thread { private ThreadPemalas sleeper; public ThreadPenggabung(String namaThread, ThreadPemalas pemalas) { super(namaThread); this.sleeper = pemalas; start(); } public void run() { try { sleeper.join(); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(getName() + " selesai setelah " + sleeper.getName()); } } public class JoinDemo { /** * @param args */ public static void main(String[] args) { ThreadPemalas brr = new ThreadPemalas("brr", 2000); ThreadPemalas grr = new ThreadPemalas("grr", 2000); ThreadPenggabung saya = new ThreadPenggabung("saya",brr); ThreadPenggabung anda = new ThreadPenggabung("anda",grr); grr.interrupt(); } }
ThreadPemalas
adalah thread yang akan ditidurkan sepanjang waktu yang diberikan pada konstruktornya. Metode run()
bisa berhenti jika waktu tidur sudah habis atau ada interupsi yang terjadi. Di dalam klausa catch
, interupsi akan dilaporkan. Fungsi isInterrupted()
melaporkan apakah thread ini diinterupsi atau tidak. Akan tetapi ketika
thread ini diinterupsi, kemudian pengecualiannya ditangkap oleh klausa catch
, misalnya, maka tanda interupsi akan segera dihapus. Oleh karenanya isInterrupted()
akan selalu bernilai false
pada program di atas. Tanda interupsi akan digunakan pada situasi lain yang mungkin berada di luar pengecualian.ThreadPenggabung
adalah thread yang menunggu hingga ThreadPemalas
selesai dengan tugasnya, yaitu dengan memanggil join()
ke objek ThreadPemalas
pada metode run()
-nya.Pada metode utama
main()
, setiap ThreadPemalas
tersambung pada ThreadPenggabung
. Dan kita lihat pada keluarannya, jika ThreadPemalas
selesai bekerja, baik karena dibangunkan melalui interupsi atau karena waktu sudah selesai, ThreadPenggabung
yang tersambung juga akan menyelesaikan tugasnya.Variasi Kode
Pada contoh-contoh di atas, semua objek thread yang kita buat diturunkan dari kelas
Thread
.
Kita hanya membuat objek yang berfungsi sebagai thread dan tidak
memiliki tugas dan fungsi lain. Akan tetapi, kelas kita mungkin saja
merupakan kelas turunan dari kelas lain. Karena Java tidak mendukung
pewarisan berganda, kita tidak bisa menurunkan kelas tersebut bersamaan
dengan kelas Thread
.Dalam hal ini, kita bisa menggunakan cara alternatif yaitu dengan mengimplementasi interface
Runnable
. Runnable
hanya memiliki satu metode untuk diimplementasi, yaitu metode run()
.Contoh berikut mendemonstrasikan contoh penggunaannya :
package com.lyracc.runnablesederhana; public class RunnableSederhana implements Runnable { private int hitungMundur = 5; public void run() { while (true) { System.out.println(Thread.currentThread().getName() + " : " + hitungMundur); if (--hitungMundur == 0) return; } } public static void main(String[] args) { for (int i = 1; i <= 5; i++) { // Buat thread baru dan jalankan Thread a = new Thread(new RunnableSederhana(), "Thread ke-" + i); a.start(); } } }
RunnableSederhana
adalah metode run()
, akan tetapi jika kita ingin melakukan hal lainnya, seperti getName()
, sleep()
, dan lainnya, kita harus secara eksplisit memberikan referensi dengan menggunakan Thread.currentThread()
.Ketika suatu kelas mengimplementasikan interface
Runnable
, artinya kelas ini memiliki metode bernama run()
, akan tetapi tidak berarti bahwa kelas ini bisa melakukan sesuatu seperti kelas Thread
atau kelas-kelas turunan yang kita buat dari kelas ini. Kita harus membuat objek Thread
sendiri seperti ditunjukkan dalam metode main()
di atas, kemudian menjalankan start()
sendiri.Kemudahan yang ditawarkan oleh interface
Runnable
adalah kemungkinan untuk menggabungkannya dengan kelas dan interface
lain. Misalnya kita ingin membuat kelas baru yang merupakan kelas
turunan dari suatu kelas lain. Kita cukup menambahkan impement Runnable
pada definisi kelasnya untuk membuat kelas yang bisa kita jadikan
thread. Dengan cara ini, kita masih bisa mengakses anggota kelas induk
secara langsung, tanpa melalui objek lain. Akan tetapi, kelas dalam
(inner class) juga bisa mengakses anggota kelas luar (outer class).
Kadang-kadang kita ingin juga membuat kelas dalam yang merupakan turunan
dari kelas Thread
.Perhatikan beberapa variasi untuk mendeklarasikan dan menggunakan thread pada contoh berikut ini.
package com.lyracc.variasithread; // Kelas dalam bernama class KelasDalamBernama { private int hitungMundur = 5; private Dalam dalam; // Kelas Dalam adalah kelas dalam (inner class) yang // merupakan kelas turunan kelas Thread private class Dalam extends Thread { Dalam(String nama) { super(nama); start(); } public void run() { while (true) { System.out.println(getName() + " : " + hitungMundur); if (--hitungMundur == 0) return; try { sleep(10); } catch (InterruptedException e) { throw new RuntimeException(e); } } } } // akhir Dalam // Konstruktor KelasDalamBernama // Membuat objek baru yang merupakan instansi kelas Dalam public KelasDalamBernama(String nama) { dalam = new Dalam(nama); } } // akhir KelasDalamBernama // Kelas dalam anonim class KelasDalamAnonim { private int hitungMundur = 5; private Thread t; // Konstruktor KelasDalamAnonim public KelasDalamAnonim(String nama) { // Kelas anonim turunan Thread t = new Thread(nama) { public void run() { while (true) { System.out.println(getName() + " : " + hitungMundur); if (--hitungMundur == 0) return; try { sleep(10); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }; // akhir kelas anonim t.start(); } } // akhir KelasDalamAnonim // Kelas dalam implementasi runnable bernama class KelasRunnableBernama { private int hitungMundur = 5; private Dalam dalam; // Kelas Dalam adalah kelas dalam (inner class) yang // merupakan kelas yang mengimplementasi Runnable private class Dalam implements Runnable { Thread t; Dalam(String nama) { t = new Thread(this, nama); t.start(); } public void run() { while (true) { System.out.println(t.getName() + " : " + hitungMundur); if (--hitungMundur == 0) return; try { Thread.sleep(10); } catch (InterruptedException e) { throw new RuntimeException(e); } } } } // akhir kelas Dalam // Konstruktor KelasRunnableBernama // Membuat objek baru yang merupakan instansi kelas Dalam public KelasRunnableBernama(String nama) { dalam = new Dalam(nama); } } // akhir KelasRunnableBernama // Kelas dalam implementasi runnable anonim class KelasRunnableAnonim { private int hitungMundur = 5; private Thread t; public KelasRunnableAnonim(String nama) { t = new Thread(new Runnable() { public void run() { while (true) { System.out.println(Thread.currentThread().getName() + " : " + hitungMundur); if (--hitungMundur == 0) return; try { Thread.sleep(10); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }, nama); // akhir kelas dalam anonim t.start(); } } // akhir KelasRunnableAnonim // Menjalankan thread dari dalam metode dan kelas anonim class ThreadViaMetode { private int hitungMundur = 5; private Thread t; private String nama; public ThreadViaMetode(String nama) { this.nama = nama; } public void runThread() { if (t == null) { // Definisi kelas anonim dari dalam metode t = new Thread(nama) { public void run() { while (true) { System.out.println(getName() + " : " + hitungMundur); if (--hitungMundur == 0) return; try { sleep(10); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }; // akhir kelas dalam anonim t.start(); } } } // akhir ThreadViaMetode public class VariasiThread { public static void main(String[] args) { new KelasDalamBernama("KelasDalamBernama"); new KelasDalamAnonim("KelasDalamAnonim"); new KelasRunnableBernama("KelasRunnableBernama"); new KelasRunnableAnonim("KelasRunnableAnonim"); new ThreadViaMetode("ThreadViaMetode").runThread(); } }
Runnable
, pada dasarnya kita menyatakan bahwa kita ingin membuat suatu proses -- yang implementasinya berada di dalam metode run()
-- bukan suatu objek yang melakukan proses tertentu. Tentunya hal ini
tergantung dari cara pandang kita, apakah kita ingin menganggap suatu
thread sebagai objek atau sesuatu yang sama sekali berbeda, yaitu
proses.Jika kita menganggap suatu thread sebagai proses, tetntunya kita akan terbebas dari cara pandang berorientasi objek yaitu "semuanya adalah objek". Artinya juga, kita tidak perlu membuat seluruh kelas menjadi
Runnable
jika hanya kita ingin memulai proses di bagian tertentu program kita.
Karenanya, mungkin lebih masuk akal untuk menyembunyikan thread di dalam
kelas kita menggunakan kelas dalam.KelasDalamBernama[.code] membuat kelas dalam yang merupakan kelas turunan dari kelas Thread, dan membuat instansi kelas ini di dalam konstruktornya. Cara ini baik jika kita ingin kelas dalam tersebut memiliki suatu kemampuan tertentu (metode lain) yang ingin kita gunakan. Akan tetapi, seringkali kita membuat thread hanya untuk memanfaatkan [code]Thread
saja, artinya kita mungkin tidak perlu membuat kelas yang memiliki nama.KelasDalamAnonim
adalah alternatif dari KelasDalamBernama
di mana kelas dalamnya merupakan kelas anonim yang merupakan kelas turunan dari kelas Thread
. Kelas anonim ini dibuat di dalam konstruktor dan disimpan dalam bentuk referensi t
bertipe Thread
. Jika metode kelas lain membutuhkan akses ke t
, maka kita bisa menggunakannya seperti Thread
biasa tanpa perlu mengetahui tipe objek t
sesungguhnya.Kelas ketiga dan keempat pada contoh di atas mirip dengan contoh pertama dan kedua, akan tetapi menggunakan interface
Runnable
. Contoh ini hanya ingin menunjukkan bahwa menggunakan Runnable
tidak menambah nilai apa-apa, kecuali membuat kodenya lebih sulit dibaca.Kelas
ThreadViaMetode
menunjukkan bagaimana membuat thread dari dalam metode. Kita bisa
memanggil metode tersebut jika kita siap untuk menjalankan thread ini.
Metode ini akan selesai setelah thread berjalan. Jika thread hanya
melakukan tugas sampingan, mungkin cara ini lebih cocok daripada
mengimplementasikan kelas khusus untuk melakukan fungsi-fungsi thread.
Tidak ada komentar:
Posting Komentar