Back to Deadlock, Livelock & Starvation
Q1Question 1 of 4

What is a deadlock?

Key Concepts
  • A deadlock occurs when two or more threads permanently block each other, each holding a lock the other needs.
  • Four Coffman conditions must all hold: mutual exclusion, hold-and-wait, no preemption, and circular wait.
  • The most practical prevention is consistent lock ordering: always acquire locks in the same deterministic order.
  • Detection at runtime uses jstack (prints 'Found one Java-level deadlock') or ThreadMXBean.findDeadlockedThreads().
  • ReentrantLock.tryLock(timeout) prevents indefinite waiting by giving up after a deadline.
  • A deadlocked JVM does not recover on its own: the affected threads are stuck forever until the process is restarted.

Definition

A deadlock occurs when two or more threads permanently block each other, each holding a lock the other needs, and the fix is to always acquire locks in a consistent order.
  • Deadlock – two or more threads are permanently blocked. Thread-1 holds Lock-A and waits for Lock-B. Thread-2 holds Lock-B and waits for Lock-A. Neither can proceed.
  • Coffman conditions – four conditions must all hold for deadlock: mutual exclusion (locks are exclusive), hold-and-wait (hold one lock while waiting for another), no preemption (locks cannot be forcibly taken), circular wait (a cycle of threads waiting for each other).
  • Prevention – break any one of the four conditions. The most practical approach is breaking circular wait through consistent lock ordering.
  • Detection – jstack automatically identifies deadlocks. Programmatically, ThreadMXBean.findDeadlockedThreads() returns the IDs of deadlocked threads.

Teller-1 is inside Rajesh's vault and needs to enter Priya's vault to complete a transfer. Teller-2 is inside Priya's vault and needs to enter Rajesh's vault. Both wait for the other to leave. Neither will leave because they need the other vault first. Both stand frozen forever. The bank halts. The fix is a rule: always enter the vault with the lower account number first.

Understanding the Concepts

What is a deadlock?

A deadlock is a situation where two or more threads are permanently blocked, each waiting for a resource held by the other. No thread can make progress. The system is frozen.

JAVA
package com.bank.core;

public class DeadlockDemo {
    private static final Object lockA = new Object();
    private static final Object lockB = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (lockA) {
                System.out.println("T1: holds lockA, waiting for lockB");
                try { Thread.sleep(50); } catch (InterruptedException e) { }
                synchronized (lockB) {
                    System.out.println("T1: holds both locks");
                }
            }
        }, "TransferThread-1");

        Thread t2 = new Thread(() -> {
            synchronized (lockB) {
                System.out.println("T2: holds lockB, waiting for lockA");
                try { Thread.sleep(50); } catch (InterruptedException e) { }
                synchronized (lockA) {
                    System.out.println("T2: holds both locks");
                }
            }
        }, "TransferThread-2");

        t1.start();
        t2.start();
        // DEADLOCK: T1 holds A, waits B. T2 holds B, waits A. Forever.
    }
}
TEXT
T1: holds lockA, waiting for lockB
T2: holds lockB, waiting for lockA
(program hangs forever)

How do you fix a deadlock with lock ordering?

Always acquire locks in the same deterministic order. If both threads lock A before B, circular wait is impossible.

JAVA
package com.bank.core;

public class DeadlockFixDemo {
    private static final Object lockA = new Object();
    private static final Object lockB = new Object();

    public static void main(String[] args) throws InterruptedException {
        // Both threads acquire lockA first, then lockB
        Thread t1 = new Thread(() -> {
            synchronized (lockA) {
                synchronized (lockB) {
                    System.out.println("T1: completed");
                }
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (lockA) { // same order as T1!
                synchronized (lockB) {
                    System.out.println("T2: completed");
                }
            }
        });

        t1.start(); t2.start();
        t1.join();  t2.join();
        System.out.println("No deadlock!");
    }
}
TEXT
T1: completed
T2: completed
No deadlock!

How do you detect a deadlock?

The JVM detects deadlocks automatically in thread dumps.

BASH
# Take a thread dump
jstack <PID>
TEXT
Found one Java-level deadlock:
=============================
"TransferThread-1":
  waiting to lock monitor 0x00007f... (object 0x000000076...)
  which is held by "TransferThread-2"
"TransferThread-2":
  waiting to lock monitor 0x00007f... (object 0x000000076...)
  which is held by "TransferThread-1"

Programmatically, you can detect deadlocks with ThreadMXBean.

JAVA
package com.bank.monitoring;

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;

public class DeadlockDetector {
    public static void checkForDeadlocks() {
        ThreadMXBean tmx = ManagementFactory.getThreadMXBean();
        long[] deadlocked = tmx.findDeadlockedThreads();
        if (deadlocked != null) {
            ThreadInfo[] infos = tmx.getThreadInfo(deadlocked, true, true);
            for (ThreadInfo info : infos) {
                System.out.println("DEADLOCK: " + info.getThreadName()
                    + " waiting for " + info.getLockName()
                    + " held by " + info.getLockOwnerName());
            }
        }
    }
}

What prevention strategies exist?

| Strategy | How it works | Breaks which condition |
|---|---|---|
| Lock ordering | Acquire locks in deterministic order | Circular wait |
| tryLock(timeout) | Give up if lock not available in time | Hold-and-wait |
| Single lock | Use one lock instead of multiple | Hold-and-wait |
| Lock-free | Use AtomicInteger, ConcurrentHashMap | Mutual exclusion |

What about tryLock as a fallback?

ReentrantLock.tryLock(timeout) gives up after a deadline instead of waiting forever.

JAVA
package com.bank.service;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class TryLockTransfer {
    public boolean transfer(ReentrantLock fromLock, ReentrantLock toLock) {
        try {
            boolean gotFrom = fromLock.tryLock(5, TimeUnit.SECONDS);
            if (!gotFrom) return false;
            try {
                boolean gotTo = toLock.tryLock(5, TimeUnit.SECONDS);
                if (!gotTo) return false;
                try {
                    // perform transfer
                    return true;
                } finally { toLock.unlock(); }
            } finally { fromLock.unlock(); }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
    }
}

What should a senior engineer know?

  • Deadlock is permanent: unlike a race condition (which may produce wrong results intermittently), a deadlock is a permanent freeze. The only recovery is restarting the JVM.
  • Database deadlocks: databases detect deadlocks and kill one transaction (the victim). Java-level deadlocks have no such automatic resolution.
  • Monitoring: schedule ThreadMXBean.findDeadlockedThreads() every 30 seconds in a health-check thread. Alert via PagerDuty if non-null.

Visual Overview

DIAGRAM
  Deadlock Cycle:

  Thread-1 ───holds───▶ Lock-A
     │                     ▲
     │ waits for           │ waits for
     ▼                     │
  Lock-B ◀───holds─── Thread-2

  Both wait forever. Neither can proceed.

  Fix: Consistent Lock Ordering

  Thread-1: lock(A) ──▶ lock(B) ──▶ done ──▶ unlock(B) ──▶ unlock(A)
  Thread-2: lock(A) ──▶ (waits) ──▶ lock(B) ──▶ done ──▶ unlock both

  No cycle. Thread-2 waits for A, gets it after Thread-1 finishes.

Key Takeaway

A deadlock occurs when two or more threads permanently block each other, each holding a lock the other needs. The four Coffman conditions must all hold simultaneously: mutual exclusion, hold-and-wait, no preemption, and circular wait. Breaking any single condition prevents deadlock.

The most practical prevention is consistent lock ordering: always acquire locks in the same deterministic order (by account ID, object hash, or any consistent comparator). For two-account transfers, both threads lock the lower account ID first. Detection uses jstack (prints the deadlock chain) or ThreadMXBean.findDeadlockedThreads() (programmatic health check).

Remember one sentence: deadlock is a permanent freeze caused by circular lock dependencies, and the fix is to always acquire locks in a consistent order.

---

Share
Use ← → arrow keys to navigate