Skip to content

Latest commit

 

History

History
132 lines (99 loc) · 3.71 KB

File metadata and controls

132 lines (99 loc) · 3.71 KB

Q10 — Deadlock in Java & How to Avoid It

Interview Tip: Show a live deadlock code that actually hangs — it's dramatic and memorable. Then show the fix.


🔑 What is Deadlock?

Deadlock happens when two or more threads wait for each other's locks forever, and none can proceed.

Classic scenario:

  • Thread 1 holds Lock A, waits for Lock B
  • Thread 2 holds Lock B, waits for Lock A
  • → Both wait forever → Deadlock

💻 Code — Deadlock Demo (This Will Hang!)

public class DeadlockDemo {

    static final Object LOCK_A = new Object();
    static final Object LOCK_B = new Object();

    public static void main(String[] args) {

        Thread thread1 = new Thread(() -> {
            synchronized (LOCK_A) {
                System.out.println("Thread1 acquired LOCK_A");
                try { Thread.sleep(100); } catch (Exception e) {}

                synchronized (LOCK_B) {                          // waits for LOCK_B
                    System.out.println("Thread1 acquired LOCK_B");
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (LOCK_B) {
                System.out.println("Thread2 acquired LOCK_B");
                try { Thread.sleep(100); } catch (Exception e) {}

                synchronized (LOCK_A) {                          // waits for LOCK_A
                    System.out.println("Thread2 acquired LOCK_A");
                }
            }
        });

        thread1.start();
        thread2.start();
        // Program hangs here — deadlock!
    }
}

✅ Fix — Always Acquire Locks in Same Order

public class DeadlockFix {

    static final Object LOCK_A = new Object();
    static final Object LOCK_B = new Object();

    public static void main(String[] args) {

        // Both threads acquire LOCK_A first, then LOCK_B
        Thread thread1 = new Thread(() -> {
            synchronized (LOCK_A) {
                synchronized (LOCK_B) {
                    System.out.println("Thread1 done");
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (LOCK_A) {   // same order as Thread1
                synchronized (LOCK_B) {
                    System.out.println("Thread2 done");
                }
            }
        });

        thread1.start();
        thread2.start();
        // No deadlock — predictable lock ordering
    }
}

📌 4 Conditions for Deadlock (Coffman's Conditions)

All 4 must be true simultaneously:

  1. Mutual Exclusion — resource held by one thread at a time
  2. Hold and Wait — thread holds one lock and waits for another
  3. No Preemption — lock can't be forcibly taken away
  4. Circular Wait — Thread1 waits for Thread2, Thread2 waits for Thread1

📌 How to Avoid Deadlock

Strategy How
Lock ordering Always acquire locks in same fixed order
Timeout Use tryLock(timeout) from ReentrantLock
Avoid nested locks Don't hold lock while acquiring another
Use java.util.concurrent ReentrantLock, Semaphore have timeout support
// tryLock with timeout — avoids deadlock
ReentrantLock lock = new ReentrantLock();
if (lock.tryLock(1, TimeUnit.SECONDS)) {
    try { /* do work */ }
    finally { lock.unlock(); }
} else {
    System.out.println("Could not acquire lock — skipping");
}

🔑 Key Points

  • Deadlock = circular dependency between threads waiting on each other's locks
  • Detect with thread dumpjstack <pid> shows "BLOCKED" threads in cycle
  • ReentrantLock.tryLock() → best prevention in production code
  • synchronized blocks can deadlock if lock order isn't consistent