What causes deadlock, and how can you prevent it?
Quick Answer
Deadlock occurs when two or more threads each hold a lock the other needs, so all of them wait forever - classically when Thread A holds Lock 1 and waits for Lock 2, while Thread B holds Lock 2 and waits for Lock 1. Prevention: always acquire multiple locks in a fixed, consistent global order, use tryLock() with a timeout to back off instead of waiting forever, minimize the scope/number of locks held at once, or use higher-level concurrency utilities that avoid manual lock ordering entirely.
Detailed Answer
Deadlock happens when two (or more) threads are each waiting for a resource the other holds, so none of them can ever proceed:
Object lockA = new Object(), lockB = new Object();
// Thread 1
synchronized (lockA) {
Thread.sleep(100);
synchronized (lockB) { /* ... */ } // waits for lockB, held by Thread 2
}
// Thread 2
synchronized (lockB) {
Thread.sleep(100);
synchronized (lockA) { /* ... */ } // waits for lockA, held by Thread 1
}
// Both threads block forever — classic deadlock
The four classic necessary conditions (Coffman conditions) are: mutual exclusion, hold-and-wait, no preemption, and circular wait — breaking any one prevents deadlock.
Prevention strategies:
- Fixed lock ordering: always acquire multiple locks in the same globally-agreed order across all threads (e.g., by a stable ID). If both threads above always locked
lockAbeforelockB, deadlock becomes impossible — this breaks the "circular wait" condition. - Lock timeouts: use
Lock.tryLock(timeout)fromjava.util.concurrent.locksinstead of an unconditional blocking acquire — if a thread can't get the second lock within the timeout, it backs off, releases what it holds, and retries, breaking "hold-and-wait." - Minimize lock scope and count: hold locks for as short a time as possible, and avoid nesting multiple locks when a single, coarser lock (or a lock-free structure) would do.
- Prefer higher-level concurrency utilities:
java.util.concurrentcollections (ConcurrentHashMap),java.util.concurrent.atomicclasses, and executor-based designs often avoid the need for manual multi-lock coordination entirely.
Detecting an existing deadlock in production: a thread dump (jstack <pid>) explicitly reports "Found one Java-level deadlock" with the involved threads and locks.