Ein Deadlock tritt auf, wenn Threads (oder wie auch immer Ihre Plattform ihre Ausführungseinheiten nennt) Ressourcen erwerben, wobei jede Ressource jeweils nur von einem Thread gehalten werden kann und diese Ressourcen so hält, dass die Holds nicht vorweggenommen werden können Zwischen den Threads besteht eine "kreisförmige" Beziehung, sodass jeder Thread im Deadlock darauf wartet, eine Ressource zu erhalten, die von einem anderen Thread gehalten wird.
Eine einfache Möglichkeit, einen Deadlock zu vermeiden, besteht darin, den Ressourcen eine vollständige Reihenfolge zu geben und die Regel festzulegen, dass Ressourcen immer nur von Threads in der richtigen Reihenfolge erfasst werden . Umgekehrt kann ein Deadlock absichtlich erstellt werden, indem Threads ausgeführt werden, die Ressourcen erfassen, diese jedoch nicht in der richtigen Reihenfolge abrufen. Beispielsweise:
Zwei Fäden, zwei Schlösser. Der erste Thread führt eine Schleife aus, die versucht, die Sperren in einer bestimmten Reihenfolge abzurufen, der zweite Thread führt eine Schleife aus, die versucht, die Sperren in der entgegengesetzten Reihenfolge abzurufen. Jeder Thread gibt beide Sperren frei, nachdem die Sperren erfolgreich erworben wurden.
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();
}
}
Nun gab es einige Kommentare in dieser Frage, die auf den Unterschied zwischen der Wahrscheinlichkeit und der Gewissheit eines Deadlocks hinweisen . In gewissem Sinne ist die Unterscheidung ein akademisches Problem. Aus praktischer Sicht würde ich mir sicherlich ein laufendes System wünschen, das nicht mit dem oben geschriebenen Code blockiert ist :)
Interviewfragen können jedoch manchmal akademisch sein, und diese SO-Frage enthält das Wort "sicher" im Titel. Was folgt, ist also ein Programm, das mit Sicherheit ins Stocken gerät. Es Locker
werden zwei Objekte erstellt, von denen jedes zwei Sperren erhält und eine CountDownLatch
zum Synchronisieren zwischen den Threads verwendet wird. Jedes Locker
Schloss sperrt das erste Schloss und zählt dann den Riegel einmal herunter. Wenn beide Fäden eine Verriegelung erhalten und die Verriegelung heruntergezählt haben, gehen sie an der Verriegelungsbarriere vorbei und versuchen, eine zweite Verriegelung zu erlangen, aber in jedem Fall hält der andere Faden bereits die gewünschte Verriegelung. Diese Situation führt zu einem gewissen Deadlock.
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();
}
}