What causes deadlock, and how can you prevent it?

9 minintermediatedeadlocklocksconcurrency

Quick Answer

Deadlock occurs when two or more threads each hold a lock the other needs, so all of them wait forever - classically when Thread A holds Lock 1 and waits for Lock 2, while Thread B holds Lock 2 and waits for Lock 1. Prevention: always acquire multiple locks in a fixed, consistent global order, use tryLock() with a timeout to back off instead of waiting forever, minimize the scope/number of locks held at once, or use higher-level concurrency utilities that avoid manual lock ordering entirely.

Detailed Answer

Deadlock happens when two (or more) threads are each waiting for a resource the other holds, so none of them can ever proceed:

Object lockA = new Object(), lockB = new Object();

// Thread 1
synchronized (lockA) {
    Thread.sleep(100);
    synchronized (lockB) { /* ... */ } // waits for lockB, held by Thread 2
}

// Thread 2
synchronized (lockB) {
    Thread.sleep(100);
    synchronized (lockA) { /* ... */ } // waits for lockA, held by Thread 1
}
// Both threads block forever — classic deadlock

The four classic necessary conditions (Coffman conditions) are: mutual exclusion, hold-and-wait, no preemption, and circular wait — breaking any one prevents deadlock.

Prevention strategies:

  1. Fixed lock ordering: always acquire multiple locks in the same globally-agreed order across all threads (e.g., by a stable ID). If both threads above always locked lockA before lockB, deadlock becomes impossible — this breaks the "circular wait" condition.
  2. Lock timeouts: use Lock.tryLock(timeout) from java.util.concurrent.locks instead of an unconditional blocking acquire — if a thread can't get the second lock within the timeout, it backs off, releases what it holds, and retries, breaking "hold-and-wait."
  3. Minimize lock scope and count: hold locks for as short a time as possible, and avoid nesting multiple locks when a single, coarser lock (or a lock-free structure) would do.
  4. Prefer higher-level concurrency utilities: java.util.concurrent collections (ConcurrentHashMap), java.util.concurrent.atomic classes, and executor-based designs often avoid the need for manual multi-lock coordination entirely.

Detecting an existing deadlock in production: a thread dump (jstack <pid>) explicitly reports "Found one Java-level deadlock" with the involved threads and locks.