Zakleszczenie ma miejsce, gdy wątki (lub cokolwiek twoja platforma nazywa swoimi jednostkami wykonawczymi) pozyskują zasoby, gdzie każdy zasób może być utrzymywany tylko przez jeden wątek naraz i zatrzymuje te zasoby w taki sposób, że blokady nie mogą być wywłaszczone, oraz istnieje pewna „cykliczna” relacja między wątkami, tak że każdy wątek w zakleszczeniu oczekuje na pobranie zasobów przechowywanych przez inny wątek.
Tak więc prostym sposobem uniknięcia impasu jest nadanie pewnego całkowitego uporządkowania zasobów i narzucenie reguły, że zasoby są pozyskiwane tylko przez wątki w kolejności . I odwrotnie, zakleszczenie można celowo utworzyć, uruchamiając wątki, które pobierają zasoby, ale nie pozyskują ich po kolei. Na przykład:
Dwie nitki, dwa zamki. Pierwszy wątek uruchamia pętlę, która próbuje uzyskać zamki w określonej kolejności, drugi wątek uruchamia pętlę, która próbuje uzyskać zamki w odwrotnej kolejności. Każdy wątek zwalnia obie blokady po pomyślnym uzyskaniu blokad.
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();
}
}
W tym pytaniu pojawiło się kilka komentarzy, które wskazują na różnicę między prawdopodobieństwem a pewnością impasu. W pewnym sensie to rozróżnienie jest kwestią akademicką. Z praktycznego punktu widzenia z pewnością chciałbym zobaczyć działający system, który nie blokuje się z kodem, który napisałem powyżej :)
Jednak pytania podczas rozmowy kwalifikacyjnej mogą czasami mieć charakter akademicki, a to pytanie SO ma w tytule słowo „na pewno”, więc poniżej znajduje się program, który z pewnością utknął w martwym punkcie. Locker
Tworzone są dwa obiekty, każdy ma dwie blokady i CountDownLatch
służy do synchronizacji między wątkami. Każdy Locker
blokuje pierwszy zamek, a następnie odlicza raz zapadkę. Kiedy obie nitki osiągną blokadę i odliczą zatrzask, przechodzą przez barierę zatrzasku i próbują uzyskać drugi zamek, ale w każdym przypadku drugi gwint już utrzymuje pożądany zamek. Ta sytuacja powoduje pewien impas.
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();
}
}