Deadlock - How to Detect and Prevent
Deadlock occurs when two or more threads are blocked forever, each waiting for a lock held by another. Typical cause: different order of lock acquisition (e.g. thread A holds L1 and waits for L2, thread B holds L2 and waits for L1). This article explains how to detect deadlocks (thread dumps, tools) and how to prevent them (lock ordering, tryLock with timeout, avoid nested locks).
Overview
- Conditions for deadlock: Mutual exclusion (locks); hold and wait (hold one lock while waiting for another); no preemption (locks cannot be taken by force); circular wait (A waits for B, B waits for A). Breaking any one condition prevents deadlock.
- Prevention: Lock ordering — always acquire locks in a global order (e.g. by object identity or by a fixed key order). Then no circular wait can occur. Alternatively use tryLock with timeout and back off if you cannot get all locks; or design so that you rarely need two locks at once.
- Detection: Thread dump (e.g.
jstack <pid>) shows blocked threads and which lock they hold and which they are waiting for. JConsole/VisualVM can detect deadlocks. Logs showing threads stuck in “waiting for lock” for a long time are a signal.
Example
Example 1: Classic deadlock
Java// Thread A synchronized (lock1) { synchronized (lock2) { doWork(); } } // Thread B synchronized (lock2) { synchronized (lock1) { doWork(); } }
- A holds lock1, waits for lock2. B holds lock2, waits for lock1. Circular wait → deadlock.
Example 2: Lock ordering — same order everywhere
JavaObject lock1 = ...; Object lock2 = ...; // Global order: lock1 before lock2 (e.g. by System.identityHashCode or by a fixed order) synchronized (lock1) { synchronized (lock2) { doWork(); } } // Every thread that needs both must acquire in this order.
- If every thread acquires lock1 then lock2, no thread can hold lock2 and wait for lock1. Circular wait is impossible.
Example 3: tryLock with timeout
Javaif (lock1.tryLock(1, TimeUnit.SECONDS)) { try { if (lock2.tryLock(1, TimeUnit.SECONDS)) { try { doWork(); } finally { lock2.unlock(); } } else { // retry or abort } } finally { lock1.unlock(); } }
- If you cannot get both locks within the timeout, release what you have and retry or fail. Avoids indefinite wait; can cause livelock if many threads keep retrying (use backoff).
Example 4: jstack deadlock output
Plain textFound one Java-level deadlock: "Thread-1": waiting to lock 0x00007f8b1c0010a0 (lock1), held by "Thread-0" "Thread-0": waiting to lock 0x00007f8b1c0010b0 (lock2), held by "Thread-1"
- jstack prints which threads are involved and which locks they hold and wait for. Use this to confirm and fix the ordering.
Core Mechanism / Behavior
- Lock ordering: Assign a total order to all locks (e.g. by address or by a business key). Always acquire in that order. No cycle → no deadlock. Challenge: knowing all locks in advance and consistent use across the codebase.
- TryLock: Non-blocking attempt; if you get one lock but not the other, release and retry. Use timeout to avoid spinning forever. Combine with backoff to reduce contention.
- Detection: JVM knows which thread holds which lock and which lock a thread is waiting on. jstack and monitoring tools use this to report deadlocks.
| Prevention | How |
|---|---|
| Lock ordering | Always acquire in same global order (e.g. lock1 then lock2) |
| TryLock + timeout | Acquire with timeout; release and retry if cannot get all |
| Single lock / no nesting | Restructure so one lock covers the critical section |
| Lock-free structures | Use ConcurrentHashMap, atomic classes, etc. to avoid explicit locks |
Key Rules
- Enforce a consistent lock order whenever multiple locks are taken. Document the order and use a helper (e.g. sort locks by id before acquiring) so new code cannot accidentally reverse the order.
- Prefer holding one lock at a time or use concurrent data structures so that nested locking is rare. Use tryLock with timeout and backoff when you must hold multiple locks.
- Run thread dumps (jstack) or use monitoring when threads appear stuck; look for “waiting to lock … held by” cycles to confirm deadlock and identify the locks involved.
What's Next
See Synchronized vs ReentrantLock for lock types and tryLock. See AQS Explained for how ReentrantLock is implemented. See ConcurrentHashMap for avoiding explicit locks.