0
0
JavaDebug / FixIntermediate · 4 min read

How to Avoid Deadlock in Java: Causes and Fixes

Deadlock in Java happens when two or more threads wait forever for each other to release locks. To avoid deadlock, always acquire locks in the same order using synchronized blocks or use higher-level concurrency utilities like ReentrantLock with tryLock. Avoid holding multiple locks at once or use timeout mechanisms to prevent waiting forever.
🔍

Why This Happens

Deadlock occurs when two threads each hold a lock the other needs and both wait forever. This happens because each thread is blocked waiting for the other to release a resource, causing a standstill.

java
public class DeadlockExample {
    private final Object lock1 = new Object();
    private final Object lock2 = new Object();

    public void method1() {
        synchronized (lock1) {
            System.out.println("Thread 1: Holding lock1...");
            try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
            synchronized (lock2) {
                System.out.println("Thread 1: Holding lock1 and lock2...");
            }
        }
    }

    public void method2() {
        synchronized (lock2) {
            System.out.println("Thread 2: Holding lock2...");
            try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
            synchronized (lock1) {
                System.out.println("Thread 2: Holding lock2 and lock1...");
            }
        }
    }

    public static void main(String[] args) {
        DeadlockExample example = new DeadlockExample();

        Thread t1 = new Thread(example::method1);
        Thread t2 = new Thread(example::method2);

        t1.start();
        t2.start();
    }
}
Output
Thread 1: Holding lock1... Thread 2: Holding lock2... // Program hangs here due to deadlock
🔧

The Fix

To fix deadlock, ensure all threads acquire locks in the same order. This prevents circular waiting. Here, both methods lock lock1 first, then lock2.

java
public class DeadlockFixed {
    private final Object lock1 = new Object();
    private final Object lock2 = new Object();

    public void method1() {
        synchronized (lock1) {
            System.out.println("Thread 1: Holding lock1...");
            try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
            synchronized (lock2) {
                System.out.println("Thread 1: Holding lock1 and lock2...");
            }
        }
    }

    public void method2() {
        synchronized (lock1) {
            System.out.println("Thread 2: Holding lock1...");
            try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
            synchronized (lock2) {
                System.out.println("Thread 2: Holding lock1 and lock2...");
            }
        }
    }

    public static void main(String[] args) {
        DeadlockFixed example = new DeadlockFixed();

        Thread t1 = new Thread(example::method1);
        Thread t2 = new Thread(example::method2);

        t1.start();
        t2.start();
    }
}
Output
Thread 1: Holding lock1... Thread 2: Holding lock1... Thread 1: Holding lock1 and lock2... Thread 2: Holding lock1 and lock2...
🛡️

Prevention

To avoid deadlocks in the future, follow these best practices:

  • Always acquire locks in a consistent global order.
  • Keep synchronized blocks short to reduce lock holding time.
  • Use higher-level concurrency utilities like java.util.concurrent.locks.ReentrantLock with tryLock() and timeouts.
  • Avoid nested locks when possible.
  • Use thread analysis tools or static code analyzers to detect potential deadlocks early.
⚠️

Related Errors

Other concurrency issues similar to deadlock include:

  • Starvation: A thread waits indefinitely because others monopolize resources.
  • Livelock: Threads keep changing state in response to each other but make no progress.
  • Race conditions: Multiple threads access shared data without proper synchronization, causing inconsistent results.

Fixes involve proper synchronization, fair locking, and avoiding circular waits.

Key Takeaways

Always acquire locks in the same order to prevent circular waiting and deadlock.
Use higher-level concurrency tools like ReentrantLock with tryLock and timeouts.
Keep synchronized blocks short and avoid nested locks when possible.
Detect potential deadlocks early using static analysis or thread monitoring tools.
Understand related concurrency issues like starvation and livelock to write safer code.