Deadlock

What is a Deadlock

A deadlock happens when two or more threads each hold a lock and wait for a lock held by the other. None can move, forever.

Thread 1: holds A ──── wants B
              ▲             │
              │             ▼
Thread 2: wants A ──── holds B
// Transfer from account a to b locks a then b.
// Thread 1: transfer(acc1, acc2)  → locks acc1, waits for acc2
// Thread 2: transfer(acc2, acc1)  → locks acc2, waits for acc1   → DEADLOCK
void transfer(Account from, Account to, int amount) {
    synchronized (from) {
        synchronized (to) {
            from.debit(amount);
            to.credit(amount);
        }
    }
}

Four Conditions

A deadlock needs all four (Coffman conditions). Break any one to prevent it.

  1. Mutual exclusion: a lock is held by one thread at a time.
  2. Hold and wait: a thread holds one lock and waits for another.
  3. No preemption: a lock cannot be taken away.
  4. Circular wait: T1 waits for T2 which waits for T1. ← easiest to break with a global lock order.

Fix: Always Lock in the Same Order

Example

Locking the account with the smaller id first means two opposite transfers can never wait on each other.

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Account a = new Account(1, 1000), b = new Account(2, 1000);
        Thread t1 = new Thread(() -> { for (int i = 0; i < 10_000; i++) Bank.transfer(a, b, 1); });
        Thread t2 = new Thread(() -> { for (int i = 0; i < 10_000; i++) Bank.transfer(b, a, 1); });
        t1.start(); t2.start();
        t1.join(); t2.join();
        System.out.println("No deadlock. a=" + a.balance + " b=" + b.balance + " total=" + (a.balance + b.balance));
    }
}

class Account {
    final int id; int balance;
    Account(int id, int balance) { this.id = id; this.balance = balance; }
}

class Bank {
    static 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.balance -= amount;
                to.balance += amount;
            }
        }
    }
}

Output

No deadlock. a=1000 b=1000 total=2000

Finding Deadlocks

Tip: Run jstack <pid> (or take a thread dump from VisualVM / IntelliJ). The JVM prints Found one Java-level deadlock with the threads and locks involved. Other fixes: tryLock(timeout) from ReentrantLock, or fewer, coarser locks.