Lesson Completion
Back to course

Deadlock and volatile: Preventing Concurrency Disasters

Advanced
25 minutes4.8Java

1. The Hook (The "Byte-Sized" Intro)

  • In a Nutshell: Deadlock occurs when threads wait for each other's locks indefinitely (circular wait).
  • Four conditions: mutual exclusion, hold and wait, no preemption, circular wait—ALL must be true.
  • Prevention: lock ordering, timeout, tryLock(). volatile ensures memory visibility—changes made by one thread visible to others. No atomicity! Use for flags, not counters. Prevents compiler optimizations that cache values in CPU registers.

Deadlock = two cars at narrow bridge, each waiting for other to reverse. volatile = megaphone announcement—everyone hears update immediately (no private notes)!


2. Conceptual Clarity (The "Simple" Tier)

💡 The Analogy

  • Deadlock: You need my pen, I need your paper. Both refuse to release. Stuck forever!
  • volatile: Shared whiteboard (everyone sees updates) vs personal notebook (private cache)

Deadlock Diagram

graph LR T1[Thread 1] -->|holds| L1[Lock A] T1 -->|waits for| L2[Lock B] T2[Thread 2] -->|holds| L2 T2 -->|waits for| L1 style T1 fill:#C2185B style T2 fill:#C2185B

3. Technical Mastery (The "Deep Dive")

Deadlock Example

java
class Resource { public synchronized void method1(Resource other) { System.out.println("Lock acquired on " + this); try { Thread.sleep(10); } catch (Exception e) {} other.method2(); // ❌ Waits for other's lock! } public synchronized void method2() { System.out.println("Lock acquired on " + this); } } // Thread 1: r1.method1(r2) // Holds r1, waits for r2 // Thread 2: r2.method1(r1) // Holds r2, waits for r1 // DEADLOCK!

volatile Example

java
class StoppableTask implements Runnable { private volatile boolean stopped = false; // ✅ volatile for visibility @Override public void run() { while (!stopped) { // Work } } public void stop() { stopped = true; // Other thread sees this immediately } }

4. Interactive & Applied Code

java
public class DeadlockDemo { static class Resource { private String name; public Resource(String name) { this.name = name; } public synchronized void acquire(Resource other) { System.out.println(Thread.currentThread().getName() + " acquired lock on " + this.name); try { Thread.sleep(100); } catch (Exception e) {} System.out.println(Thread.currentThread().getName() + " trying to acquire " + other.name); synchronized(other) { System.out.println("Acquired both locks!"); } } @Override public String toString() { return name; } } public static void main(String[] args) { Resource r1 = new Resource("Resource-1"); Resource r2 = new Resource("Resource-2"); // ❌ DEADLOCK: Different lock order Thread t1 = new Thread(() -> r1.acquire(r2), "Thread-1"); Thread t2 = new Thread(() -> r2.acquire(r1), "Thread-2"); t1.start(); t2.start(); // Threads will deadlock—program hangs! // ✅ DEADLOCK PREVENTION: Same lock order demonstratePreventionWithOrdering(); demonstrateVolatile(); } static void demonstratePreventionWithOrdering() { // Always acquire locks in same order (by ID) Resource r1 = new Resource("R1"); Resource r2 = new Resource("R2"); Runnable task = () -> { // Always lock in same order synchronized(r1) { synchronized(r2) { System.out.println("Acquired both safely"); } } }; new Thread(task).start(); new Thread(task).start(); } static void demonstrateVolatile() { VolatileExample example = new VolatileExample(); Thread writer = new Thread(() -> { try { Thread.sleep(1000); } catch (Exception e) {} example.stop(); System.out.println("Stop flag set"); }); Thread reader = new Thread(() -> { example.run(); System.out.println("Reader stopped"); }); reader.start(); writer.start(); } } class VolatileExample { private volatile boolean running = true; public void run() { while (running) { // Without volatile, this might loop forever! // Compiler might cache 'running' in register } } public void stop() { running = false; } }

5. The Comparison & Decision Layer

Featuresynchronizedvolatile
Visibility✅ Yes✅ Yes
Atomicity✅ Yes❌ No
PerformanceSlower (locking)Faster (no lock)
Use forCritical sectionsFlags, status variables

6. The "Interview Corner" (The Edge)

The "Killer" Interview Question: "Why can't you use volatile for a counter?" Answer: volatile doesn't guarantee atomicity! count++ is 3 operations:

java
private volatile int count = 0; public void increment() { count++; // ❌ NOT ATOMIC! // 1. Read count // 2. Increment // 3. Write count // Another thread can interfere between steps! } // ✅ Solutions: // 1. synchronized public synchronized void increment() { count++; } // 2. AtomicInteger private AtomicInteger count = new AtomicInteger(0); public void increment() { count.incrementAndGet(); }

Pro-Tip: Deadlock prevention strategies:

  1. Lock ordering: Always acquire locks in same global order
  2. Timeout: Use tryLock(timeout) instead of lock()
  3. Lock hierarchy: Assign numeric levels, always lock lower→higher
  4. Avoid nested locks: Minimize complexity
java
// ✅ GOOD: Lock ordering by ID void transfer(Account from, Account to, int amount) { Account first = from.id < to.id ? from : to; Account second = from.id < to.id ? to : from; synchronized(first) { synchronized(second) { from.debit(amount); to.credit(amount); } } }

Topics Covered

Java FundamentalsConcurrency

Tags

#java#multithreading#threads#concurrency#parallelism

Last Updated

2025-02-01