Dalam tutorial concurrency Java ini, saya akan coba memandu Anda bagaimana membuat thread, kemudian melakukan operasi dasar thread seperti start, pause, interrupt & join. Anda bakal memahami secara persis bagaimana thread bekerja di level terendah java.

1. Cara create Thread di Java
Ada 2 cara untuk melakukannya, yaitu dengan cara meng-extend (menurunkan Thread class) atau dengan mengimplementasikan Runnable interface. Keduanya merupakan bagian dari java.lang package, jadi kamu tidak perlu repot-repot mengimportnya. Letakan kode yang diperlukan untuk dieksekusi di thread terpisah di dalam method run(), yang mana hasil override dari Thread/Runnable. Sementara itu method start() digunakan untuk menjalankan Thread dengan status (alive).
Berikut adalah pertunjukan antara main thread (milik sistem java) dan thread tambahan yang saya buat.
public class ThreadExample1 extends Thread {
public void run() {
System.out.println("My name is: " + getName());
}
public static void main(String[] args) {
ThreadExample1 t1 = new ThreadExample1();
t1.start();
System.out.println("My name is: " + Thread.currentThread().getName());
}
}
Saya akan coba jelaskan bagaimana kode di atas bekerja. Perhatikan ThreadExample1 class yang meng-extend Thread class dan override run() method. Dalam method run(), saya mencoba untuk mengidentifikasi thread buatan dengan cara mencetaknya di console / terminal, sehingga saya tahu apa nama yang dipakai oleh sistem untuk thread buatan saya itu. Sebenarnya saat program dijalankan Thread main lah yang pertama kali dieksekusi, sehingga main thread-lah yang memicu t1.start() jadi saya bisa mengetahui nama-nama thread itu dengan cara memanggil method .getName()
Static method currentThread() akan mengembalikan nilai Thread yang berasosiasi dengan-nya. Anda dapat melakukan ini di asosiasi yang lain, seperti Runnable() mungkin. Outputnya kita bisa lihat.
My name is: Thread-0
My name is: main
Sekarang kita tahu bahwa ada 2 thread di sana:
- Thread-0: adalah nama thread yang saya buat.
- main: adalah nama thread utama yang otomatis berjalan sebagai ruh program Java itu sendiri.
Thread-0 segera berakhir setelah method run() complete mengeksekusi perintah-perintah yang ada di dalamnya, begitu pula thread main yang berakhir dengan cara yang sama.
Hal yang menarik di sini adalah jika kamu menjalankan program itu lagi lebih dari satu kali, maka kamu akan melihat bahwa kadang kala Thread-0 yang pertama kali dijalankan, kadang thread main yang pertama. Ini semua dapat kita identifikasi dengan cara mencetak nama threadnya. Oke, sederhananya kita sebut saja itu radomly. Artinya, eksperiman diatas menunjukkan bahwa tidak ada jaminan bahwa thread yang pertama dieksekusi akan selalu menjadi yang pertama berjalan, karena sebenarnya mereka itu berjalan secara serentak (concurrently). Kamu harus mengingat perilaku ini sehubungan dengan konteks multi-threading.
Sekarang, saya menggunakan cara ke-2 yaitu mengimplementasikan Runnable interface. Kode di bawah ini adalah definisi saya tentang ThreadExample2 class dengan mengoverride method run() sebagai buah fungsi dari Runnable.
public class ThreadExample2 implements Runnable {
public void run() {
System.out.println("My name is: " + Thread.currentThread().getName());
}
public static void main(String[] args) {
Runnable task = new ThreadExample2();
Thread t2 = new Thread(task);
t2.start();
System.out.println("My name is: " + Thread.currentThread().getName());
}
}
Kamu bisa perhatikan bahwa di sana terdapat perbedaan kecil dibanding kode sebelumnya, dimana object bertipe Runnable dibuat dan dilewatkan ke konstruktor Thread object(t2). Object Runnable dapat ditampilkan sebagai tugas yang terpisah dari thread yang mengeksekusinya.
Kedua program berperilaku sama, jadi apa pro-kontra masing-masing cara tersebut?
- Extending Thread class, dapat digunakan untuk kasus sederhana, sebab Java tidak mengizinkan banyak pewarisan kelas jika kamu hendak memperluas fungsionalitasnya.
- Implementasi Runnable interface adalah cara paling flexible yang memungkinkan kamu memperluas fungsionalitas class-mu, sebab Java mengizinkan Implementasi lebih dari satu interface.
Perlu kamu ingat juga bahwa thread yang berakhir setelah method run(), maka dia akan dimasukkan ke dalam dead state dan tidak dapat di-start lagi. Kamu tidak akan pernah bisa me-restart thread mati.
Kamu juga dapat memberikan nama untuk thread yang kamu buat, bisa via constructor Thread class atau via setName(), contoh.
Thread t1 = new Thread("First Thread");
Thread t2 = new Thread();
t2.setName("Second Thread");
2. Cara pause Thread
Selain menjalankan, kamu juga dapat menghentikan sementara thread yang sedang berjalan saat ini, caranya dengan memohon kepada static method sleep(miliseconds) dari Thread class, maka dia akan tertidur selama waktu yang telah kamu tentukan (dalam detik), contoh.
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
// code to resume or terminate...
}
Kode diatas akan menidurkan thread selama 2 detik (atau 2000 miliseconds), setelah itu akan berjalan lagi secara normal.
InterruptedException akan memeriksa pengecualian jika suatu hal tidak terpenuhi, jadi kamu harus menangani ini, apakah perlu dilempar ke class khusus, ui khsusu, log khusus, atau terserah kamu, yang penting jangan biarkan sistem menangani ini karena dia tidak handal dalam hal intuisi, dia akan terang-terangan menampilkan error yang terjadi.
Saya berikan contoh bagaimana program NumberPrint memperbarui 5 angka setiap 2 detik.
public class NumberPrint implements Runnable {
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(i);
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
System.out.println("I'm interrupted");
}
}
}
public static void main(String[] args) {
Runnable task = new NumberPrint();
Thread thread = new Thread(task);
thread.start();
}
}
Catatan: kamu tidak bisa mem-pause sebuah thread dari thread lain. Hanya thread itu sendiri yang dapat menghentikan sementara eksekusi jobnya. Selain itu, tidak ada jaminan bahwa setiap thread yang tidur selalu benar-benar tidur, sebab setiap thread bisa saling menginterrupt, jadi jika dia dalam kondisi lemah (sleep), maka bisa saja system mendelegasikan memorinya kepada thread lain yang lebih aktif. Mengenai hal ini, nanti kita akan bahas lebih lanjut di kesempatan lain.
3. Cara interrupt Thread
"Sesorang memotong pembicaraan orang lain", mungkin ini adalah ilustrasi yang mudah Anda pahami manakala melakukan operasi Interrupt, sama halnya suatu perbincangan partisipan bisa saling menyela, maka sesama thread juga bisa saling menyela / menghentikan sementara, hal ini dapat dilakukan dengan cara melibatkan fungsi stop dan resume, keduanya dapat dieksekusi dari thread lain.
Berikut adalah contoh statement Interrupt untuk thread t1.
t1.interrupt();
Hal lain yang perlu Anda perhatikan adalah manakalah melakukan interrupt pada thread yang sedang tertidur, ini akan memicu interruptedExeption, selanjutkan keputusan ada di tangan Anda, apakah ingin membangunkan thread yang tertidur tadi atau membatalkan, atau melakukan job lain. Apapun keputusan yang Anda ambil silakan definisikan di blok kode cacth{..}.
Berikut kami berikan contoh sederhana dengan skenario thread t1 mencetak pesan setiap 2 detik, dan main thread menyelanya setelah 5 detik.
public class ThreadInterruptExample implements Runnable {
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println("This is message #" + i);
try {
Thread.sleep(2000);
continue;
} catch (InterruptedException ex) {
System.out.println("I'm resumed");
}
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new ThreadInterruptExample());
t1.start();
try {
Thread.sleep(5000);
t1.interrupt();
} catch (InterruptedException ex) {
// do nothing
}
}
}
Sebagaimana tampak pada blok catch di method run(), dia melanjutkan looping ketika thread di-interrupt. Maksudnya, thread diminta melanjutkan pekerjaan saat dia sedang tertidur.
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
System.out.println("I'm resumed");
continue;
}
Tapi jika Anda menghendaki dia berhenti, maka cukup ubah statement diakhir blok catch dengan return, seperti ini.
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
System.out.println("I'm about to stop");
return;
}
Statement return diatas dimaksudkan untuk mengakhiri pekerjaan thread, sehingga dia berstatus mati (goes to dead state).
Bagaimana jika mengakhiri thread yang tertidur? apakah tidak perlu handling InterruptedException?
Dalam kasus seperti itu, Anda perlu memeriksa status interupsi dari thread saat ini, menggunakan salah satu metode berikut dari kelas Thread:
- interrupted(), method statis ini mengembalikan true jika thread saat ini telah di-interrupt, atau false jika tidak. Perhatikan! bahwa metode ini sekaligus menghapus status interupsi, yang berarti bahwa jika mengembalikan true, maka status interupsi diatur ke false, begitu juga sebaliknya.
- isInterrupted(), method statis ini prinsipnya sama, mengembalikan nilai true atau false, hanya saja tidak merubah status thread saat ini.
Sekarang, kita dapat modifikasi kode di atas untuk memberikan contoh Thread Interruption dengan pengecekan status.
public class ThreadInterruptExample implements Runnable {
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println("This is message #" + i);
if (Thread.interrupted()) {
System.out.println("I'm about to stop");
return;
}
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new ThreadInterruptExample());
t1.start();
try {
Thread.sleep(5000);
t1.interrupt();
} catch (InterruptedException ex) {
// do nothing
}
}
}
Sekarang versi di atas tidak sama dengan sebelumnya, karena t1 berakhir sangat cepat, karena dia tidak tertidur dan statement pencetak juga dieksekusi lebih cepat. Jadi, contoh ini hanya sekedar menunjukkan bagaimana cara menggunakannya. Dalam prakteknya, pemeriksaan status interupsi seperti ini harus diterapkan untuk operasi jangka panjang seperti I/O, jaringan, database, dll.
Ingat!, bahwa ketika InterruptedException dilempar, status interupsi dihapus.
Jika Anda melihat kelas Thread di Javadocs, Anda akan melihat 4 metode ini:
destroy() - stop() - suspend() - resume()
Namun semua metode ini sudah usang, artinya Anda tidak boleh menggunakannya. Mari kita gunakan mekanisme interupsi yang telah saya jelaskan sejauh ini.
4. Cara Membuat Thread Menunggu Thread Lain (Join)
Skenario ini disebut dengan join, berguna saat Anda ingin current thread (thread yang sedang berjalan saat ini) menunggu thread lain selesai dulu. Barulah current thread berjalan lagi, contoh.
t1.join();
Statement di atas menyebabkan current thread menunggu thread t1 selesai terlebih dahulu. Di bawah ini kami berikan contoh main thread sebagai current thread yang menunggu thread t1.
public class ThreadJoinExample implements Runnable {
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println("This is message #" + i);
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
System.out.println("I'm about to stop");
return;
}
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new ThreadJoinExample());
t1.start();
try {
t1.join();
} catch (InterruptedException ex) {
// do nothing
}
System.out.println("I'm " + Thread.currentThread().getName());
}
}
Program di atas, main thread (current thread) akan selalu dalam kondisi terminates sampai thread t1 selesai. Oleh sebab itu pesan "I'm main" selalu dicetak terakhir kali.
This is message #1
This is message #2
This is message #3
This is message #4
This is message #5
This is message #6
This is message #7
This is message #8
This is message #9
This is message #10
I'm main
Catatan: method join() akan melempar InterruptedException, mana kala current thread dalam keadaan ter-interupsi, jadi Anda perlu menghandle ini dengan catch exeption.
Terdapat 2 overload method join():
- join(milliseconds)
- join(milliseconds, nanoseconds)
Kedua method itu menyebabkan current thread menunggu selama waktu yang telah ditentukan. Maksudnya, itu toleransi waktu tunggu terhadap thread lain, jika ternyata thread lain belum selesai juga selama waktu tersebut, maka current thread mengabaikan thread lain itu dan berjalan seperti sedia kala.
Anda juga dapat men-join multiple threads dengan current thread, seperti ini.
t1.join();
t2.join();
t3.join();
Kasus di atas akan membuat current thread menunggu ketiga thread itu selesai terlebih dahulu.
Demikianlah fundamentals penggunaan thread di java, semoga bermanfaat, jangan lupa share, dan selamat belajar!