Back to Deadlock, Livelock & Starvation
Q4Question 4 of 4

Deadlock vs livelock vs starvation?

Key Concepts
  • Deadlock: threads are permanently BLOCKED, each holding a lock the other needs. Zero CPU, zero progress. Detected by jstack ('Found Java-level deadlock') and ThreadMXBean.findDeadlockedThreads().
  • Livelock: threads are RUNNING (RUNNABLE state) but make no progress because they continuously react to each other without advancing. High CPU, zero throughput.
  • Starvation: a thread is perpetually denied access because other threads always get priority. The thread is WAITING or BLOCKED but never gets its turn.
  • Deadlock detection is automatic in jstack; livelock requires correlating high CPU with zero throughput; starvation requires monitoring per-thread wait times.
  • Fix deadlock with lock ordering or tryLock with timeout. Fix livelock with random backoff between retries. Fix starvation with fair locks.
  • Deadlock threads are BLOCKED (near zero CPU). Livelock threads are RUNNABLE (100% CPU). This distinction is critical for diagnosis.
  • In thread dumps, deadlocked threads show 'waiting to lock' and the lock owner chain forms a cycle.

Definition

Deadlock is threads BLOCKED forever in a circular lock wait (zero CPU), livelock is threads RUNNING but making no progress (high CPU, zero throughput), and starvation is a thread perpetually denied access (never gets its turn).
  • 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") and ThreadMXBean.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.

JAVA
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.

JAVA
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

DIAGRAM
  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).

---

Share
Use ← → arrow keys to navigate