Liveness Hazards: Deadlock, Livelock, and Starvation
Fast recall
Quick revision
Core answer
Ability to identify and reason about liveness failures — a sign of production concurrency experience.
Conflating deadlock (stuck forever) with livelock (threads keep running but make no progress). Both prevent progress but have very different root causes and detection strategies.
Mention lock ordering as the canonical deadlock prevention strategy, jstack/ThreadMXBean for runtime detection, and that tryLock() with timeout can break deadlocks at the cost of retry logic.
Interview-ready guide
Interview answer
Deadlock, livelock, and starvation are three distinct failure modes — they differ in what the threads are doing, how you diagnose them, and how you prevent them.
Deadlock:
- Two threads each holding what the other needs — Thread A holds Lock 1 and waits for Lock 2; Thread B holds Lock 2 and waits for Lock 1; neither proceeds
- Fix with lock ordering: all threads acquire locks in the same global order — circular wait condition can't form
- Diagnosis:
jstackautomatically detects monitor cycles and prints "Found N deadlock" at the bottom
Livelock:
- Threads are not blocked — they're actively running but keep reacting to each other without making progress
- Like two people stepping left and right in a hallway, each trying to let the other pass
- Fix with randomized backoff — break the symmetry
Starvation:
- A thread is perpetually denied resources — unfair locks let new arrivals barge ahead of waiting threads indefinitely
- Fix with fair locks or explicit priority management — trades throughput for guaranteed FIFO ordering
Prevention tools:
tryLock(timeout)breaks hold-and-wait by releasing locks on timeout — essential for dynamic locking scenariosThreadMXBean.findDeadlockedThreads()for programmatic detection in health-check endpoints
I recommend lock ordering as the primary prevention strategy and tryLock with timeouts as the fallback.
Liveness Hazards: Deadlock, Livelock, and Starvation
Direct Answer
Deadlock: threads wait forever in a cycle of lock dependencies. Livelock: threads keep changing state responding to each other but never progress. Starvation: a thread never gets CPU time or a lock because others always take priority. All three prevent useful work without necessarily crashing the JVM.
Deep Dive & Architecture
Six proven techniques, roughly in order of effectiveness: (1) Lock ordering — always acquire locks in the same global order (e.g., by lock identity hash or by a documented precedence).
Eliminates cyclic waits by construction. (2) tryLock(timeout) — use ReentrantLock.tryLock(1, SECONDS) instead of unconditional lock(); if it fails, release everything held and retry. (3) Open calls — never call external methods (callbacks, listeners, other services) while holding a lock; release first. (4) Reduce lock scope — minimize the code inside synchronized blocks; narrow the critical section. (5) Lock-free alternatives — ConcurrentHashMap, AtomicReference, immutable data + atomic reference swap. (6) Single global lock — simple, no cycles possible; fine for low-contention scenarios.
Bonus: prefer ReentrantLock over synchronized on Java 21 (no virtual-thread pinning, plus tryLock support). Actively monitor with ThreadMXBean.findDeadlockedThreads(). Most production deadlocks come from inconsistent lock ordering — enforce it via code review and static analysis.
Common Mistakes & Pitfalls
Conflating deadlock (stuck forever) with livelock (threads keep running but make no progress). Both prevent progress but have very different root causes and detection strategies.
Check your understanding
Answer these without looking back at the article.
- How do you detect a deadlock in a running Java application using a thread dump?
- When do Java parallel streams improve performance, and when do they actually hurt it?
- How do you prevent deadlocks in Java? What design techniques avoid them?
Related interview prep categories
2Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.