Definition
- Deadlock – two or more threads are permanently blocked, each waiting for a lock held by the other. Thread state: BLOCKED. CPU usage: near zero. No exception thrown. Detected by
jstack(prints "Found Java-level deadlock") andThreadMXBean.findDeadlockedThreads(). - Livelock – threads are RUNNING (not blocked) but make no useful progress. They continuously change state in response to each other without advancing. Thread state: RUNNABLE. CPU: 100%. Throughput: zero. Harder to detect because jstack does not report it as a deadlock.
- Starvation – a thread is denied access to a resource because other threads always take priority. Common with non-fair locks, low thread priority, or reader-heavy ReadWriteLock. The thread may be WAITING or BLOCKED but never gets served.
- Detection – deadlock: automatic (jstack, ThreadMXBean). Livelock: high CPU + zero throughput (manual correlation). Starvation: per-thread wait time monitoring (one thread's wait time is much longer than others).
At National Bank of India: deadlock is two tellers each holding a key the other needs, both standing frozen forever. Livelock is two tellers in a narrow corridor repeatedly stepping aside for each other: "After you!" "No, after you!" Both keep moving but neither gets past. Starvation is a teller who always lets VIP customers cut in line: the regular customer waits forever, never served.
Understanding the Concepts
What is a deadlock?
Deadlock occurs when two or more threads form a circular chain of lock dependencies. Thread-1 holds lock-A and waits for lock-B. Thread-2 holds lock-B and waits for lock-A. Neither can proceed.
package com.bank.concurrency;
import java.math.BigDecimal;
public class DeadlockDemo {
private static final Object lockA = new Object();
private static final Object lockB = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (lockA) {
try { Thread.sleep(50); } catch (InterruptedException e) { }
synchronized (lockB) { System.out.println("Thread-1 done"); }
}
}, "Rajesh-Transfer").start();
new Thread(() -> {
synchronized (lockB) {
try { Thread.sleep(50); } catch (InterruptedException e) { }
synchronized (lockA) { System.out.println("Thread-2 done"); }
}
}, "Priya-Transfer").start();
// DEADLOCK: Rajesh holds A, waits for B. Priya holds B, waits for A.
}
}
What is a livelock?
Livelock occurs when threads continuously react to each other without making progress. Both threads are actively running (RUNNABLE state) and consuming CPU, but neither advances toward completion.
package com.bank.concurrency;
import java.util.concurrent.locks.ReentrantLock;
public class LivelockDemo {
private static final ReentrantLock lockA = new ReentrantLock();
private static final ReentrantLock lockB = new ReentrantLock();
public static void main(String[] args) {
new Thread(() -> {
while (true) {
lockA.lock();
if (lockB.tryLock()) {
try { System.out.println("Thread-1 done"); return; }
finally { lockB.unlock(); lockA.unlock(); }
}
lockA.unlock(); // give up, retry
// Without backoff: both threads retry in sync → livelock!
}
}, "Worker-1").start();
new Thread(() -> {
while (true) {
lockB.lock();
if (lockA.tryLock()) {
try { System.out.println("Thread-2 done"); return; }
finally { lockA.unlock(); lockB.unlock(); }
}
lockB.unlock(); // give up, retry
}
}, "Worker-2").start();
// Both keep acquiring/releasing in sync: LIVELOCK
// Fix: add random backoff: Thread.sleep(random * 2^attempt)
}
}
How do you detect each one?
| Hazard | Thread state | CPU | Detection method |
|---|---|---|---|
| Deadlock | BLOCKED | Near 0% | jstack: "Found Java-level deadlock" |
| Livelock | RUNNABLE | 100% | CPU + zero throughput correlation |
| Starvation | WAITING/BLOCKED | Varies | Per-thread wait time monitoring |
How do you fix each one?
- Deadlock: consistent lock ordering (always acquire lock-A before lock-B), tryLock with timeout, or avoid nested locks entirely.
- Livelock: random backoff between retries.
Thread.sleep(ThreadLocalRandom.current().nextLong(10, 100))breaks the synchronization between competing threads. - Starvation: fair locks (
new ReentrantLock(true)), equal thread priorities, or redesigning the access pattern.
What are common interview follow-up questions?
- Can ThreadMXBean detect livelock? No.
findDeadlockedThreads()only detects threads in BLOCKED state waiting for locks. Livelock threads are RUNNABLE. - Can a deadlock resolve itself? No. Without external intervention (kill thread, timeout), deadlock is permanent.
Visual Overview
Deadlock vs Livelock vs Starvation ═══════════════════════════════════ Deadlock: Thread-1: BLOCKED → holds A, waits B Thread-2: BLOCKED → holds B, waits A CPU: 0%. Progress: NONE. Fix: lock ordering. Livelock: Thread-1: RUNNABLE → lock A → tryLock B fails → unlock A → retry Thread-2: RUNNABLE → lock B → tryLock A fails → unlock B → retry CPU: 100%. Progress: NONE. Fix: random backoff. Starvation: Thread-1: always acquires lock (barging) Thread-2: WAITING forever, never gets turn CPU: varies. Progress: partial. Fix: fair lock.
Key Takeaway
Three concurrency hazards produce zero useful progress through different mechanisms. Deadlock: threads BLOCKED in a circular lock chain, zero CPU. Detected automatically by jstack and ThreadMXBean. Fixed by consistent lock ordering or tryLock with timeout. Livelock: threads RUNNABLE, constantly reacting to each other without advancing, 100% CPU but zero throughput. Detected by correlating high CPU with zero throughput. Fixed by random backoff. Starvation: one thread perpetually denied access because others always get priority. Fixed by fair locks.
The critical diagnostic distinction is thread state: BLOCKED means deadlock, RUNNABLE with high CPU means livelock. Deadlock is the easiest to diagnose (jstack prints it automatically). Livelock is the hardest (no automatic detection, requires manual correlation).
At National Bank of India, a deadlock was diagnosed in 5 minutes using jstack. A livelock took 2 hours because jstack showed no deadlock message and engineers had to correlate 100% CPU with zero transaction throughput.
Remember one sentence: deadlock is BLOCKED (zero CPU, automatic detection), livelock is RUNNABLE (high CPU, zero throughput), and starvation is WAITING forever (unfair scheduling).
---