O deadlock acontece quando os threads (ou o que quer que sua plataforma chame de unidades de execução) adquirem recursos, onde cada recurso só pode ser mantido por um thread de cada vez e retém esses recursos de tal forma que as suspensões não podem ser impedidas, e existe algum relacionamento "circular" entre os threads de modo que cada thread no conflito está esperando para adquirir algum recurso mantido por outro thread.
Portanto, uma maneira fácil de evitar o conflito é dar uma ordem total aos recursos e impor uma regra de que os recursos só são adquiridos por threads em ordem . Por outro lado, um deadlock pode ser criado intencionalmente executando threads que adquirem recursos, mas não os adquirem em ordem. Por exemplo:
Dois fios, duas fechaduras. O primeiro thread executa um loop que tenta adquirir os bloqueios em uma determinada ordem, o segundo thread executa um loop que tenta adquirir os bloqueios na ordem oposta. Cada thread libera ambos os bloqueios após adquiri-los com sucesso.
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();
}
}
Agora, houve alguns comentários nesta questão que apontam a diferença entre o probabilidade e a certeza de um impasse. Em certo sentido, a distinção é uma questão acadêmica. Do ponto de vista prático, certamente gostaria de ver um sistema em execução que não bloqueie com o código que escrevi acima :)
No entanto, as perguntas da entrevista podem ser acadêmicas às vezes, e essa pergunta do SO tem a palavra "certamente" no título, então o que se segue é um programa que certamente bloqueia. Dois Locker
objetos são criados, cada um recebe dois bloqueios e um CountDownLatch
usado para sincronizar entre os threads. Cada Locker
trava a primeira fechadura e, em seguida, conta uma vez. Quando os dois fios adquirem um bloqueio e fazem a contagem regressiva do trinco, passam pela barreira do trinco e tentam obter um segundo bloqueio, mas em cada caso o outro segmento já contém o bloqueio desejado. Essa situação resulta em um certo 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();
}
}