Advance Komputer in Java
Dosen : Mauldy Laya
Kuliah 1 (30 Agustus 2012)
BAB 1. Multithreading
Java adalah bahasa pemrograman banyak thread, yang
artinya beberapa hal bisa dilakukan bersama-sama. Thread
adalah unit terkecil dari eksekusi suatu program. Thread mengeksekusi rangkaian
instruksi satu demi satu. Ketika sistem menjalankan program, komputer akan
membuat thread baru. Instruksi-instruksi dalam program akan dieksekusi oleh
thread ini secara berantai, satu demi satu dari awal hingga akhir. Thread
disebut "mati" jika program selesai dieksekusi.
Dalam sistem
komputer modern, beberapa thread bisa tercipta dalam satu waktu. Pada satu saat
tertentu, hanya ada satu thread yang bisa dijalankan, karena CPU hanya bisa
melakukan satu hal dalam satu waktu. (Pada komputer dengan multiprosesor,
multicore, dan hyper-threading, masing-masing prosesor atau core melakukan
thread yang berbeda-beda). Akan tetapi sebenarnya komputer membagi waktu
menjadi bagian-bagian kecil sehingga seolah-olah seluruh thread dijalankan
secara bersama-sama. Pembagian waktu berarti CPU
mengeksekusi suatu thread dalam kurun waktu tertentu, setelah itu beralih
mengeksekusi thread yang lain, kemudian thread lain, dan seterusnya dan
kemudian kembali ke thread pertama lagi kira-kira 100 kali per detik. Di mata
user, semua thread berjalan pada saat yang sama.
Cara termudah untuk membuat thread adalah membuat
kelas turunan dari 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.
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). 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.
berbeda dengan eksekusi ke 2
Yielding (menghasilkan)
Misalnya pada contoh di atas, kita bisa mengganti isi metode
run()
dengan
Maka outputnya akan berubah seperti ini :
Secara umum, yield mungkin berguna untuk situasi yang agak langka, dan kita
tidak bisa menggunakannya secara serius untuk memperbaiki kinerja aplikasi
kita.
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 :
Maka outputbya berubah seperti 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.
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 :
Pada contoh di atas, kita ubah konstruktornya
untuk mengeset prioritas kemudian menjalankan thread. Pada metode
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.
Perintah
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
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 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();
}
}
Hasiil keluarannya adalah sebagai berikut :
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 :
Hasil outputmya adalah sebagai berikut :
Satu-satunya yang dibutuhkan oleh kelas
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
.
Tidak ada komentar:
Posting Komentar