Java - Kekerasan Thread

Halo di sana, ahli penyihir Java masa depan! Hari ini, kita akan melangkah ke salah satu konsep yang paling menantang dalam pengaturcaraan Java: Kekerasan Thread. Jangan khawatir jika ia terdengar menakutkan - pada akhir pelajaran ini, kamu akan menjadi penyelidik kekerasan, dapat mengesan dan mengatasinya masalah-masalah yang menyusahkan ini seperti profesional!

Java - Thread Deadlock

Apa itu Kekerasan Thread?

Bayangkan kamu di atas raksa penyajian makan malam, dan kamu perlu raksa dan garpu untuk makan. Kamu rakam raksa, tetapi apabila kamu mencubit garpu, kawan kamu sudah mengambilkannya. Pada masa yang sama, kawan kamu perlu raksa kamu untuk makan, tetapi kamu tidak akan melepaskan raksa kamu melainkan kamu mendapat garpu. Kedua-dua kamu kena kunci, menunggu yang lain untuk melepaskan apa yang kamu perlukan. Itu, kawan-kawan, adalah kekerasan dalam kehidupan sebenar!

Di dalam Java, kekerasan terjadi apabila dua atau lebih thread dihalang selamanya, masing-masing menunggu yang lain untuk melepaskan sumber daya. Ia seperti pertarungan berantai Meksiko, tetapi dengan thread Java menggantikan kowboy!

Memahami Thread dan Penyegerakan

Sebelum kita melangkah lebih mendalam ke atas kekerasan, mari kita ikhtisarkan beberapa konsep utama:

Thread

Thread adalah seperti pekerja kecil di atas program kamu, masing-masing melakukan tugas yang spesifik. Mereka boleh bekerja secara serentak, menjadikan program kamu lebih efisien.

Penyegerakan

Penyegerakan adalah satu cara untuk memastikan hanya satu thread boleh mengakses sumber daya bersama pada satu masa. Ia seperti meletakkan tanda "Jangan Diganggu" di atas pintu raksa di atas hotel.

Bagaimana Kekerasan Terjadi

Kekerasan biasanya terjadi apabila empat kondisi (diketahui sebagai Kondisi Coffman) dipenuhi:

  1. Pengecualian Mutlak: Setidaknya satu sumber daya harus dipegang dalam modus yang tidak dapat dibagi.
  2. Pegang dan Tunggu: Satu thread harus mengpegang setidaknya satu sumber daya sementara menunggu untuk mendapatkan sumber daya tambahan yang dipegang oleh thread lain.
  3. Tidak Ada Pemaksaan: Sumber daya tidak boleh diambil paksa dari satu thread; mereka harus dilepaskan secara sukarela.
  4. Tunggu Berputar: Rantai berputar dua atau lebih thread, masing-masing menunggu sumber daya yang dipegang oleh thread berikutnya di atas rantai.

Contoh: Mempersembahkan Keadaan Kekerasan

Mari kita lihat contoh klasik tentang keadaan kekerasan. Kita akan membuat dua sumber daya (diwakili oleh Objek) dan dua thread yang mencoba untuk mendapatkan sumber daya ini dalam urutan yang berbeza.

public class ContohKekerasan {
private static Object sumberDaya1 = new Object();
private static Object sumberDaya2 = new Object();

public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (sumberDaya1) {
System.out.println("Thread 1: Mengpegang Sumber Daya 1...");
try { Thread.sleep(100); } catch (InterruptedException e) {}
System.out.println("Thread 1: Menunggu Sumber Daya 2...");
synchronized (sumberDaya2) {
System.out.println("Thread 1: Mengpegang Sumber Daya 1 dan Sumber Daya 2");
}
}
});

Thread thread2 = new Thread(() -> {
synchronized (sumberDaya2) {
System.out.println("Thread 2: Mengpegang Sumber Daya 2...");
try { Thread.sleep(100); } catch (InterruptedException e) {}
System.out.println("Thread 2: Menunggu Sumber Daya 1...");
synchronized (sumberDaya1) {
System.out.println("Thread 2: Mengpegang Sumber Daya 2 dan Sumber Daya 1");
}
}
});

thread1.start();
thread2.start();
}
}

Mari kita pecahkan ini:

  1. Kita membuat dua Object instance, sumberDaya1 dan sumberDaya2, yang mewakili sumber daya bersama kita.
  2. Kita membuat dua thread:
  • thread1 mencoba untuk mendapatkan sumberDaya1 dahulu, kemudian sumberDaya2.
  • thread2 mencoba untuk mendapatkan sumberDaya2 dahulu, kemudian sumberDaya1.
  1. Kedua-dua thread menggunakan kata kunci synchronized untuk mengunci sumber daya.
  2. Kita menambahkan penundaan kecil (Thread.sleep(100)) untuk meningkatkan kesempatan terjadinya kekerasan.

Apabila kamu menjalankan kode ini, ia berkemungkinan mengakibatkan kekerasan. Thread 1 akan mendapatkan sumberDaya1 dan menunggu sumberDaya2, sementara Thread 2 akan mendapatkan sumberDaya2 dan menunggu sumberDaya1. Tidak satu pun thread dapat berlanjut, mengakibatkan kekerasan.

Contoh Penyelesaian Kekerasan

Sekarang bahwa kita telah lihat bagaimana kekerasan dapat terjadi, mari kita lihat bagaimana kita dapat menghindarinya. Satu penyelesaian sederhana adalah untuk selalu mendapatkan sumber daya dalam urutan yang sama di atas semua thread.

public class ContohPenyelesaianKekerasan {
private static Object sumberDaya1 = new Object();
private static Object sumberDaya2 = new Object();

public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (sumberDaya1) {
System.out.println("Thread 1: Mengpegang Sumber Daya 1...");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (sumberDaya2) {
System.out.println("Thread 1: Mengpegang Sumber Daya 1 dan Sumber Daya 2");
}
}
});

Thread thread2 = new Thread(() -> {
synchronized (sumberDaya1) {
System.out.println("Thread 2: Mengpegang Sumber Daya 1...");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (sumberDaya2) {
System.out.println("Thread 2: Mengpegang Sumber Daya 1 dan Sumber Daya 2");
}
}
});

thread1.start();
thread2.start();
}
}

Dalam penyelesaian ini:

  1. Kedua-dua thread sekarang mendapatkan sumberDaya1 dahulu, kemudian sumberDaya2.
  2. Ini memastikan bahwa ada urutan konsisten dalam pengambilan sumber daya, mencegah kondisi tunggu berputar.

Praktik Terbaik untuk Menghindari Kekerasan

  1. Selalu mendapatkan kunci dalam urutan yang sama: Seperti yang kita lihat di atas contoh penyelesaian, ini mencegah tunggu berputar.
  2. Hindari kunci bersarang: Cobalah untuk minimalkan jumlah blok yang disinkronkan.
  3. Gunakan tryLock() dengan masa tamat: Sebagai ganti untuk menunggu tanpa batas, gunakan tryLock() dengan masa tamat untuk mencoba mendapatkan kunci untuk masa tertentu.
  4. Hindari untuk mengpegang kunci untuk masa yang panjang: Lepaskan kunci segera setelah kamu selesai dengan sumber daya bersama.

Kesimpulan

Selamat! Kamu baru saja membuka misteri tentang kekerasan thread Java. Ingat, untuk menulis program multi-thread adalah seperti mengoreografi tarian yang kompleks - ia memerlukan perancangan dan koordinasi yang hati-hati untuk memastikan semua penari (thread) bergerak lancar tanpa melanggar kaki satu sama lain (atau mengunci sumber daya satu sama lain).

Sebagai kamu teruskan perjalananmu di Java, ingatkan konsep ini, dan kamu akan dipersiapkan dengan baik untuk menulis program multi-thread yang efisien dan bebas kekerasan. Happy coding, dan semoga thread kamu selalu dalam harmoni!

Credits: Image by storyset