Explanation
What just happened
T1 took lock A, T2 took lock B. Then each needed the other’s lock. T1 waited for T2 to release B, and T2 waited for T1 to release A. Neither could move, so both stayed BLOCKED forever: no exception, no CPU use, just a program that stops.
Try the what-if switch
same lock order: both threads take A first. Whoever loses the race for A waits holding nothing, so it can’t block anyone. T1 finished, released both locks, and T2 went next.
The four conditions
A deadlock needs all of these at once:
- Locks are exclusive.
- A thread holds one lock while waiting for another.
- Locks can’t be taken away.
- There’s a cycle: T1 waits for T2, which waits for T1.
Break any one and deadlock is impossible. A global lock order breaks the cycle, and it’s the usual fix.
Other fixes
ReentrantLock.tryLock(timeout): give up and retry instead of waiting forever.- Hold one lock at a time, and don’t call unknown code (like listeners) while holding a lock.
Finding one
Take a thread dump with jstack <pid> or your IDE. The JVM detects monitor deadlocks and prints “Found one Java-level deadlock” with both threads and the locks they hold. Paste the dump into the thread dump reader to see it in plain words.
Common questions
What causes a deadlock in Java?
Two threads take the same locks in opposite orders. Each holds one lock and waits for the other's, so both stay BLOCKED forever, with no exception and no CPU use.
How do you prevent a deadlock?
Take locks in the same global order everywhere. A thread that loses the race for the first lock waits holding nothing, so it can't block anyone. ReentrantLock.tryLock(timeout) is another way out: give up and retry.
How do I find a deadlock?
Take a thread dump with jstack <pid> or your IDE. The JVM reports "Found one Java-level deadlock" with both threads and the locks they hold. It finds the deadlock but won't break it.
Verified: this timeline matches a real program, checked in CI on JDK 21 and 25. How we verify. Unclear or wrong? Tell us