Le blocage se produit lorsque les threads (ou tout ce que votre plate-forme appelle ses unités d'exécution) acquièrent des ressources, où chaque ressource ne peut être détenue que par un thread à la fois, et conserve ces ressources de telle sorte que les réservations ne peuvent pas être anticipées, et il existe une relation "circulaire" entre les threads de telle sorte que chaque thread dans le blocage attend d'acquérir une ressource détenue par un autre thread.
Ainsi, un moyen simple d'éviter les blocages est de donner un ordre total aux ressources et d'imposer une règle selon laquelle les ressources ne sont acquises que par les threads dans l'ordre . Inversement, un blocage peut être intentionnellement créé en exécutant des threads qui acquièrent des ressources, mais ne les acquièrent pas dans l'ordre. Par exemple:
Deux fils, deux verrous. Le premier thread exécute une boucle qui tente d'acquérir les verrous dans un certain ordre, le second thread exécute une boucle qui tente d'acquérir les verrous dans l'ordre opposé. Chaque thread libère les deux verrous après avoir réussi l'acquisition des verrous.
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();
}
}
Maintenant, il y a eu quelques commentaires dans cette question qui soulignent la différence entre la vraisemblance et la certitude d'une impasse. Dans un certain sens, la distinction est une question académique. D'un point de vue pratique, j'aimerais certainement voir un système en cours d'exécution qui ne se bloque pas avec le code que j'ai écrit ci-dessus :)
Cependant, les questions d'entrevue peuvent parfois être théoriques, et cette question SO a le mot «sûrement» dans le titre, donc ce qui suit est un programme qui bloque certainement . Deux Locker
objets sont créés, chacun reçoit deux verrous et un CountDownLatch
utilisé pour se synchroniser entre les threads. Chacun Locker
verrouille le premier verrou, puis compte à rebours le verrou une fois. Lorsque les deux fils ont acquis un verrou et ont décompté le verrou, ils passent devant la barrière de verrou et tentent d'acquérir un second verrou, mais dans chaque cas, l'autre fil détient déjà le verrou souhaité. Cette situation entraîne une certaine impasse.
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();
}
}