Question 17 · Section 9

What is deadlock?

Two people on a narrow bridge from opposite sides:

Language versions: English Russian Ukrainian

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

  1. Lock acquisition order cannot be guaranteed — different code paths acquire locks in different orders
  2. There are external calls inside the lock (DB, HTTP, file system) — an external resource may block and create deadlock
  3. 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

  1. Lock Ordering — always acquire resources in a defined order
  2. tryLock(timeout) — use ReentrantLock with a timeout
  3. Avoid nested locks — if possible
  4. Lock-free structures — ConcurrentHashMap, Atomic*
  5. Thread-specific resources — sharding, independent data
  6. Monitor via ThreadMXBean.findDeadlockedThreads()
  7. jstack — first tool when deadlock is suspected
  8. 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]]