What causes a deadlock in Java, and what are the four conditions that must all hold for one to happen?
A deadlock is when two or more threads each hold a lock the other needs, so neither can proceed. The classic case is two threads locking two objects in opposite orders: thread A takes lock1 then wants lock2, thread B takes lock2 then wants lock1, and both wait forever.
The four conditions are the ones every interview asks for. Mutual exclusion — a lock can only be held by one thread at a time. Hold and wait — a thread holds one lock while waiting for another. No preemption — a lock cannot be forcibly taken away from the thread holding it. Circular wait — there is a cycle in the wait-for graph, so every thread is waiting for a thread that is waiting for it.
Java satisfies the first three automatically, which is why the interesting one is the fourth. You cannot make a monitor pre-emptible, and you cannot stop a thread holding a lock from waiting for another. So deadlock prevention in Java is almost entirely about breaking the circular wait — by establishing a global order for lock acquisition and always taking locks in that order, a cycle becomes impossible.
Two things make deadlocks more common than people expect. Nested locks acquired in different orders in different methods are the usual culprit, especially in code where the order was never written down. And wait on a condition object that is not the one you are being notified on is a deadlock that looks like a lost notification.
Here is the same thing as runnable code:
public class Main {
public static void main(String[] args) throws InterruptedException {
Object lockA = new Object();
Object lockB = new Object();
Thread t1 = new Thread(() -> {
synchronized (lockA) {
sleepQuietly(50);
synchronized (lockB) {
System.out.println("t1 got both locks");
}
}
}, "t1");
Thread t2 = new Thread(() -> {
synchronized (lockB) {
sleepQuietly(50);
synchronized (lockA) {
System.out.println("t2 got both locks");
}
}
}, "t2");
t1.setDaemon(true);
t2.setDaemon(true);
t1.start();
t2.start();
t1.join(500);
t2.join(500);
System.out.println("t1 state: " + t1.getState());
System.out.println("t2 state: " + t2.getState());
System.out.println("both are BLOCKED waiting for each other");
}
static void sleepQuietly(long ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}