skip to content

What is a deadlock in a multithreaded Java program, and can you give a concrete example of how it happens?

level: juniorimportance: must knowfreq 78%

answer

  1. Cyclic wait on locks held by each other
  2. Two threads, two locks, opposite acquisition order
  3. Program hangs, doesn't crash (liveness failure)
  4. Threads stuck in BLOCKED state
  5. Timing-dependent, hides in tests

basics

~20 s

A deadlock is when two or more threads each hold a lock the other needs, so all of them wait forever and none can make progress. Example: thread A holds lock 1 and waits for lock 2, while thread B holds lock 2 and waits for lock 1.

solid answer

~40 s

A deadlock is a state where two or more threads are each blocked waiting for a resource (typically a lock/monitor) that another blocked thread is holding, forming a cycle so none can ever proceed. The classic example: thread A does synchronized(lock1){ synchronized(lock2){...} } while thread B does synchronized(lock2){ synchronized(lock1){...} }. If A grabs lock1 and B grabs lock2 at the same moment, A waits for lock2 (held by B) and B waits for lock1 (held by A) forever. Deadlock is a liveness failure: the program does not crash, it just hangs with threads stuck in BLOCKED state. It's distinct from a livelock (threads active but making no progress) and starvation (a thread perpetually denied a resource). Reproduction is timing-dependent, so it can hide in testing and surface in production.

code

java · 14 lines
java
class Account { final Object lock = new Object(); long balance; }

void transfer(Account from, Account to, long amt) {
    synchronized (from.lock) {          // step 1: lock 'from'
        synchronized (to.lock) {        // step 2: lock 'to'
            from.balance -= amt;
            to.balance   += amt;
        }
    }
}

// Thread A: transfer(a, b, 10)  -> locks a, waits for b
// Thread B: transfer(b, a, 10)  -> locks b, waits for a
// If A and B interleave between step 1 and step 2, they deadlock.

go deeper

for a junior

Can define deadlock as threads waiting on each other forever and sketch the two-thread, two-lock example.

for a middle

Distinguishes deadlock from livelock/starvation, knows it's a liveness (not safety) failure and is timing-dependent.

for a senior

Frames it in terms of a cyclic wait-for graph and connects the example to lock-ordering as the fix.

for a principal

Discusses why deadlocks evade testing, the cost in distributed/DB contexts, and architectural choices that make deadlock structurally impossible.

## What a lock is In Java, when multiple threads share mutable data, you protect it with a **lock** (also called a **monitor**) so only one thread touches the data at a time. The `synchronized` keyword acquires the lock on entry and releases it on exit; `java.util.concurrent.locks.Lock` (e.g. `ReentrantLock`) does the same explicitly with `lock()`/`unlock()`. While one thread holds a lock, any other thread that tries to acquire the *same* lock is parked in the **BLOCKED** state until the holder releases it. ## What a deadlock is A **deadlock** happens when a set of threads are blocked **in a cycle**: each thread in the set holds a lock that the next thread in the cycle is waiting for. Because every thread is waiting and none is running, no lock is ever released, so the wait is permanent. The program does not throw an exception or crash — it simply **hangs**. This is a **liveness** failure (the program fails to make progress) as opposed to a **safety** failure (the program produces wrong results). ## The canonical two-lock example Imagine a bank transfer that locks the *from* account then the *to* account: ``` Thread A: transfer(acct1 -> acct2) locks acct1, then tries to lock acct2 Thread B: transfer(acct2 -> acct1) locks acct2, then tries to lock acct1 ``` Interleaving that deadlocks: 1. A acquires acct1's lock. 2. B acquires acct2's lock. 3. A tries to acquire acct2 -> blocked (B holds it). 4. B tries to acquire acct1 -> blocked (A holds it). Now A waits for B and B waits for A: a cycle. Neither will ever release, so both hang forever. ## Why it's sneaky The deadlock only occurs if the two threads interleave at *just* the wrong moment (A grabbing lock1 while B grabs lock2). Most of the time one thread finishes before the other starts, so tests pass and the bug appears intermittently — often only under production load. That timing dependence is what makes deadlocks notoriously hard to catch. ## Related but different failures - **Livelock:** threads are not blocked — they keep responding to each other (e.g. both back off and retry in lockstep) but make no progress. - **Starvation:** a thread is perpetually denied a resource it needs (e.g. low-priority threads never scheduled), while others proceed. Deadlock specifically means a *cyclic, permanent* wait. ## How you'd derive an answer Think: shared locks + a cycle of who-waits-for-whom + permanence. If you can describe two threads acquiring two locks in opposite orders, you've described the simplest deadlock.

  • Can a single thread deadlock itself on one ReentrantLock?
    No. ReentrantLock (and synchronized) are reentrant: the same thread can re-acquire a lock it already holds without blocking. A self-deadlock needs a non-reentrant lock, or two locks acquired in conflicting order across threads.
  • How is deadlock different from a livelock?
    In a deadlock the threads are blocked (BLOCKED state) and do nothing. In a livelock they're active and keep changing state in response to each other but still make no forward progress, e.g. two threads that both detect contention and politely back off forever.

saying these in an interview costs you the question

  • Confusing deadlock (cyclic permanent block) with livelock (active but no progress) or starvation
  • Saying a deadlock throws an exception or crashes the JVM — it hangs silently
  • Thinking a single lock can deadlock by itself (reentrant locks allow the same thread to re-acquire)
  • Claiming deadlock always reproduces — it's timing-dependent

context