Terjadi kebuntuan ketika utas (atau apa pun yang disebut platform Anda sebagai unit eksekusinya) memperoleh sumber daya, di mana setiap sumber daya hanya dapat disimpan oleh satu utas pada satu waktu, dan menyimpan sumber daya tersebut sedemikian rupa sehingga penangguhan tidak dapat dilakukan sebelumnya, dan terdapat beberapa hubungan "melingkar" antara utas sedemikian rupa sehingga setiap utas di jalan buntu menunggu untuk memperoleh beberapa sumber daya yang dipegang oleh utas lain.
Jadi, cara mudah untuk menghindari kebuntuan adalah dengan memberikan beberapa pengurutan total ke sumber daya dan menerapkan aturan bahwa sumber daya hanya dapat diperoleh dengan utas secara berurutan . Sebaliknya, kebuntuan dapat dibuat dengan sengaja dengan menjalankan utas yang memperoleh sumber daya, tetapi tidak mendapatkannya secara berurutan. Sebagai contoh:
Dua utas, dua kunci. Utas pertama menjalankan putaran yang mencoba mendapatkan kunci dalam urutan tertentu, utas kedua menjalankan putaran yang mencoba mendapatkan kunci dalam urutan yang berlawanan. Setiap utas melepaskan kedua kunci setelah berhasil memperoleh kunci.
public class HighlyLikelyDeadlock {
static class Locker implements Runnable {
private Object first, second;
Locker(Object first, Object second) {
this.first = first;
this.second = second;
}
@Override
public void run() {
while (true) {
synchronized (first) {
synchronized (second) {
System.out.println(Thread.currentThread().getName());
}
}
}
}
}
public static void main(final String... args) {
Object lock1 = new Object(), lock2 = new Object();
new Thread(new Locker(lock1, lock2), "Thread 1").start();
new Thread(new Locker(lock2, lock1), "Thread 2").start();
}
}
Sekarang, ada beberapa komentar dalam pertanyaan ini yang menunjukkan perbedaan antara kemungkinan dan kepastian deadlock. Dalam beberapa hal, perbedaan adalah masalah akademis. Dari sudut pandang praktis, saya pasti ingin melihat sistem yang berjalan tidak mengalami kebuntuan dengan kode yang saya tulis di atas :)
Namun, pertanyaan wawancara terkadang bisa bersifat akademis, dan pertanyaan SO ini memang memiliki kata "pasti" di judulnya, jadi berikut ini adalah program yang pasti menemui jalan buntu. Dua Locker
objek dibuat, masing-masing diberi dua kunci dan CountDownLatch
digunakan untuk menyinkronkan antar utas. Masing-masing Locker
mengunci kunci pertama lalu menghitung mundur kait satu kali. Ketika kedua utas telah memperoleh kunci dan menghitung mundur kait, mereka melanjutkan melewati penghalang kait dan mencoba mendapatkan kunci kedua, tetapi dalam setiap kasus utas lainnya sudah memegang kunci yang diinginkan. Situasi ini menghasilkan kebuntuan tertentu .
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class CertainDeadlock {
static class Locker implements Runnable {
private CountDownLatch latch;
private Lock first, second;
Locker(CountDownLatch latch, Lock first, Lock second) {
this.latch = latch;
this.first = first;
this.second = second;
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
try {
first.lock();
latch.countDown();
System.out.println(threadName + ": locked first lock");
latch.await();
System.out.println(threadName + ": attempting to lock second lock");
second.lock();
System.out.println(threadName + ": never reached");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public static void main(final String... args) {
CountDownLatch latch = new CountDownLatch(2);
Lock lock1 = new ReentrantLock(), lock2 = new ReentrantLock();
new Thread(new Locker(latch, lock1, lock2), "Thread 1").start();
new Thread(new Locker(latch, lock2, lock1), "Thread 2").start();
}
}