Deadlocks

⭐ Interview Importance: MEDIUM
⏱️ Revision Time: 5 min

TL;DR

  • A Deadlock is a catastrophic state where two or more threads are blocked forever, waiting for each other to release locks.
  • Example: Thread A has Lock 1 and is waiting for Lock 2. Thread B has Lock 2 and is waiting for Lock 1. Neither can proceed.
  • It completely freezes the affected threads in the application.

Concept

For a deadlock to occur, four conditions (Coffman Conditions) must simultaneously hold true:

  1. Mutual Exclusion: Resources cannot be shared; only one thread can hold a lock at a time.
  2. Hold and Wait: A thread holding a lock can wait to acquire additional locks.
  3. No Preemption: A lock cannot be forcefully taken away from a thread; it must be released voluntarily.
  4. Circular Wait: There exists a circular chain of threads, each waiting for a lock held by the next thread in the chain.

If you break any one of these four rules, a deadlock is mathematically impossible.

Examples

public class DeadlockExample {
    public static void main(String[] args) {
        final Object Lock1 = new Object();
        final Object Lock2 = new Object();
        
        Thread t1 = new Thread(() -> {
            synchronized (Lock1) {
                System.out.println("Thread 1: Holding lock 1...");
                try { Thread.sleep(10); } catch (Exception e) {}
                
                System.out.println("Thread 1: Waiting for lock 2...");
                synchronized (Lock2) { // BLOCKS FOREVER HERE
                    System.out.println("Thread 1: Acquired lock 1 & 2");
                }
            }
        });
        
        Thread t2 = new Thread(() -> {
            synchronized (Lock2) {
                System.out.println("Thread 2: Holding lock 2...");
                try { Thread.sleep(10); } catch (Exception e) {}
                
                System.out.println("Thread 2: Waiting for lock 1...");
                synchronized (Lock1) { // BLOCKS FOREVER HERE
                    System.out.println("Thread 2: Acquired lock 1 & 2");
                }
            }
        });
        
        t1.start();
        t2.start();
    }
}

Interview Questions

Q: How do you prevent Deadlocks in Java code?
A: The easiest and most common way to prevent deadlocks is to break the Circular Wait condition. You do this by establishing a strict, globally agreed-upon order in which locks must be acquired. In the example above, if we enforce a rule that says “Always acquire Lock1 before Lock2”, Thread 2 would be rewritten to request Lock1 first. It would block waiting for Thread 1, allowing Thread 1 to safely acquire Lock2 and finish its work.

Q: How do you diagnose a deadlock in a running application?
A: If a Java server freezes in production, you can take a Thread Dump using command-line tools like jstack <pid> or jcmd <pid> Thread.print. A thread dump takes a snapshot of the JVM. It will explicitly identify the blocked threads and state: Found one Java-level deadlock: followed by the exact class and line numbers where the threads are permanently stuck.