What is deadlock?
Two people on a narrow bridge from opposite sides:
Junior Level
Basic Understanding
Deadlock is a situation where two or more threads block each other permanently.
In code: Thread 1 holds lockA and waits for lockB. Thread 2 holds lockB and waits for lockA. Neither will release its lock first. Both threads are stuck forever.
Simple Analogy
Two people on a narrow bridge from opposite sides:
- Person A: “I won’t let you pass until you let me pass”
- Person B: “I won’t let you pass until you let me pass”
- Result: Both stand forever
Example in Java
public class DeadlockDemo {
private final Object lock1 = new Object();
private final Object lock2 = new Object();
public void methodA() {
synchronized(lock1) {
System.out.println("Thread 1: Acquired lock1");
// Pause so the other thread can acquire lock2
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized(lock2) { // WAITING for lock2 — but the other thread has it!
System.out.println("Thread 1: Acquired lock2");
}
}
}
public void methodB() {
synchronized(lock2) {
System.out.println("Thread 2: Acquired lock2");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized(lock1) { // WAITING for lock1 — but the other thread has it!
System.out.println("Thread 2: Acquired lock1");
}
}
}
}
// Launch:
new Thread(demo::methodA).start();
new Thread(demo::methodB).start();
// → DEADLOCK! Thread 1 waits for lock2, Thread 2 waits for lock1
Deadlock vs Livelock vs Starvation
| Type | Behavior | CPU |
|---|---|---|
| Deadlock | Threads “sleep” and never wake up | 0% |
| Livelock | Threads constantly react to each other but make no progress | 100% |
| Starvation | Thread is ready to work but never gets the resource | Depends |
Livelock Example
// Two people in a corridor — both try to step aside, but in the same direction
while (otherPersonMoving) {
stepAside(); // Constantly reacting, but not passing
}
Starvation Example
// Thread with low priority never gets CPU
Thread lowPriority = new Thread(task);
lowPriority.setPriority(Thread.MIN_PRIORITY); // Always waits
Middle Level
Wait-for-Graph
In theory, deadlock is represented as a cycle in a graph:
Thread 1 ──waits──→ Lock B ──held by──→ Thread 2
▲ │
│ │
└───────────waits──────── Lock A ◄─────────┘
held by
CYCLE = DEADLOCK
Coffman Conditions
Deadlock is possible ONLY when all 4 conditions are met simultaneously:
| # | Condition | Description |
|---|---|---|
| 1 | Mutual Exclusion | A resource can be held by only one thread |
| 2 | Hold and Wait | A thread holds one resource and waits for another |
| 3 | No Preemption — if a thread has acquired synchronized(lock), no one can forcibly take it away. Only the thread itself can exit synchronized. |
|
| 4 | Circular Wait | There is a circular chain of waiting |
Database Deadlocks
Deadlock can also occur at the database level:
// Thread 1:
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
// Thread 2:
UPDATE accounts SET balance = balance - 50 WHERE id = 2;
UPDATE accounts SET balance = balance + 50 WHERE id = 1;
// → DEADLOCK! The DB will detect and roll back one transaction
Difference: The DB can detect deadlock and automatically roll back one transaction. In Java, you have to detect it manually.
Hidden Deadlock
// Deadlock through Thread Pool Starvation
ExecutorService pool = Executors.newFixedThreadPool(1);
// Step-by-step:
// Step 1: pool.submit(A) — the only thread starts executing task A
// Step 2: Inside A, pool.submit(B).get() is called — task B is queued
// Step 3: .get() blocks thread A waiting for B
// Step 4: The only thread is busy, B will never start = DEADLOCK
// This is called "thread pool starvation deadlock"
Future<String> future = pool.submit(() -> {
// This task waits for another task from the same pool
return pool.submit(() -> "result").get(); // DEADLOCK!
});
// One thread is busy with the first task, the second task waits in queue
Static Initialization Deadlock
// Class A
class A {
static {
System.out.println(B.value); // References B
}
}
// Class B
class B {
static {
System.out.println(A.value); // References A
}
}
// Two threads load A and B simultaneously → deadlock in ClassLoader
Senior Level
When NOT to Use Nested synchronized
- Lock acquisition order cannot be guaranteed — different code paths acquire locks in different orders
- There are external calls inside the lock (DB, HTTP, file system) — an external resource may block and create deadlock
- Unknown code — you pass a lock to someone else’s method, which may acquire another lock
Alternatives: ReentrantLock.tryLock(timeout), lock-free structures (ConcurrentHashMap, AtomicReference), or a single shared lock.
Under the Hood: JVM Deadlock Detection
The JVM has a built-in deadlock detector via ThreadMXBean:
ThreadMXBean mxBean = ManagementFactory.getThreadMXBean();
long[] deadlockedThreads = mxBean.findDeadlockedThreads();
if (deadlockedThreads != null) {
for (long threadId : deadlockedThreads) {
ThreadInfo info = mxBean.getThreadInfo(threadId, true, true);
System.out.println("Deadlock: " + info.getThreadName());
System.out.println("Blocked on: " + info.getLockName());
System.out.println("Blocked by: " + info.getLockOwnerName());
}
}
Important: This is an expensive operation — don’t run it too often.
Deadlock in Highload
Problem: Deadlock can paralyze a system in milliseconds
JVM Detector: Runs periodically (not instantly)
Result: threads that need the blocked resources will hang forever. The rest of the system continues to work, but throughput drops.
Edge Cases
Deadlock with External Systems
Java thread: holds lockA → waits for DB response
DB transaction: holds table lock → waits for HTTP from Java app
→ DEADLOCK between Java and DB!
ReentrantLock and Interruption
// synchronized cannot be interrupted — thread will hang until JVM restart
synchronized(lock) {
// Deadlock here = eternal wait
}
// ReentrantLock can be interrupted
lock.lockInterruptibly();
try {
// Deadlock here = can be interrupted via thread.interrupt()
} finally {
lock.unlock();
}
Diagnostics
jstack — the fastest method
jstack -l <pid>
Output:
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x00007f... (object 0x000000076af0c8d0),
which is held by "Thread-2"
"Thread-2":
waiting to lock monitor 0x00007f... (object 0x000000076af0c8e0),
which is held by "Thread-1"
VisualVM / JConsole
“Check Deadlock” button — instant check.
Programmatic Check
// Periodic check (every 30 seconds)
ScheduledExecutorService monitor = Executors.newSingleThreadScheduledExecutor();
monitor.scheduleAtFixedRate(() -> {
ThreadMXBean mxBean = ManagementFactory.getThreadMXBean();
long[] deadlocked = mxBean.findDeadlockedThreads();
if (deadlocked != null) {
log.error("DEADLOCK DETECTED! Threads: {}", Arrays.toString(deadlocked));
// Alert, restart, etc.
}
}, 0, 30, TimeUnit.SECONDS);
Prevention Rule: Lock Ordering
The most effective prevention method — always acquire resources in a strictly defined order:
// Define order via hashCode
void transfer(Account from, Account to, int amount) {
Object first = System.identityHashCode(from) < System.identityHashCode(to) ? from : to;
Object second = first == from ? to : from;
synchronized(first) {
synchronized(second) {
from.withdraw(amount);
to.deposit(amount);
}
}
}
Best Practices
- Lock Ordering — always acquire resources in a defined order
- tryLock(timeout) — use ReentrantLock with a timeout
- Avoid nested locks — if possible
- Lock-free structures — ConcurrentHashMap, Atomic*
- Thread-specific resources — sharding, independent data
- Monitor via ThreadMXBean.findDeadlockedThreads()
- jstack — first tool when deadlock is suspected
- Use ReentrantLock — instead of synchronized for interruptibility
Interview Cheat Sheet
Must know:
- Deadlock = two threads block each other forever (Thread 1 holds lockA and waits for lockB, Thread 2 holds lockB and waits for lockA)
- 4 Coffman conditions (all simultaneously): Mutual Exclusion, Hold and Wait, No Preemption, Circular Wait
- Deadlock vs Livelock vs Starvation: deadlock — threads sleep (0% CPU), livelock — react but no progress (100% CPU), starvation — thread never gets the resource
- Lock Ordering — main prevention: always acquire resources in a strict order (e.g., by hashCode)
- ReentrantLock.tryLock(timeout) — avoids eternal waiting, unlike synchronized
- JVM detects deadlock via ThreadMXBean.findDeadlockedThreads(), but it’s an expensive operation
- Hidden deadlock: thread pool starvation — a task waits for .get() from another task in the same pool (especially single-thread pool)
- DB automatically detects and rolls back deadlock, Java — does not (needs manual monitoring)
Frequent follow-up questions:
- How to prevent deadlock? — Lock ordering (fixed acquisition order), tryLock(timeout), lock-free structures, one shared lock
- How does synchronized differ from ReentrantLock in deadlock? — synchronized cannot be interrupted (eternal wait), ReentrantLock.lockInterruptibly() can be interrupted via thread.interrupt()
- What is thread pool starvation deadlock? — A task in the pool waits for .get() from another task in the same pool; if the pool is single-thread — deadlock is guaranteed
- How to detect deadlock in production? — jstack -l
, ThreadMXBean.findDeadlockedThreads(), VisualVM "Check Deadlock"
Red flags (DO NOT say):
- “Deadlock is impossible in my code” — it’s often hidden (nested synchronized, external calls)
- “JVM automatically resolves deadlock” — no, JVM only detects, but does not resolve (unlike DB)
- “shutdownNow() will fix deadlock” — no, it sends interrupt(), but synchronized cannot be interrupted
- “One lock is enough to prevent it” — no, deadlock occurs with 2+ resources in different orders
Related topics:
- [[10. How do AtomicInteger, AtomicLong work]]
- [[11. What is the advantage of Atomic classes over synchronized]]
- [[12. What is a Thread Pool]]
- [[15. What does ExecutorService do]]