Deadlock in Java: How It Happens and How to Avoid It

Illustration for Deadlock in Java: How It Happens and How to Avoid It
By Last updated:

Multithreading can boost performance, but it also brings risks — and deadlocks are among the most dangerous. A deadlock occurs when two or more threads are waiting for each other to release resources, resulting in a system freeze.

This tutorial explores what causes deadlocks, how to identify them, and the best techniques to prevent and recover from them in Java applications.


🔍 What Is a Deadlock?

A deadlock is a situation in which two or more threads are blocked forever, each waiting for the other to release a resource.

Real-world analogy

Imagine two people trying to pass each other in a narrow hallway. One moves left, the other right — both blocking each other indefinitely.


🧵 Thread Lifecycle and Deadlocks

Deadlocks occur when threads are BLOCKED due to locked resources.

Java thread lifecycle: NEW → RUNNABLE → RUNNING → BLOCKED/WAITING → TERMINATED

Deadlocks freeze progress at the BLOCKED stage.


🧪 Java Example of a Deadlock

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

    public void method1() {
        synchronized (lock1) {
            System.out.println("Thread 1 acquired lock1");
            try { Thread.sleep(100); } catch (InterruptedException ignored) {}
            synchronized (lock2) {
                System.out.println("Thread 1 acquired lock2");
            }
        }
    }

    public void method2() {
        synchronized (lock2) {
            System.out.println("Thread 2 acquired lock2");
            try { Thread.sleep(100); } catch (InterruptedException ignored) {}
            synchronized (lock1) {
                System.out.println("Thread 2 acquired lock1");
            }
        }
    }
}

This creates a classic deadlock scenario when both threads try to acquire locks in reverse order.


🔄 Conditions That Cause Deadlock

  1. Mutual exclusion – Resources can’t be shared.
  2. Hold and wait – Threads hold one resource and wait for another.
  3. No preemption – Resources can’t be forcibly taken away.
  4. Circular wait – Threads form a circular chain of waiting.

All four must occur for a deadlock to happen.


🛠 Techniques to Avoid Deadlock

1. Lock ordering

Always acquire locks in a consistent global order.

synchronized (lock1) {
    synchronized (lock2) {
        // Safe: consistent order
    }
}

2. Try-lock with timeout

Use ReentrantLock.tryLock() to avoid indefinite blocking.

if (lock1.tryLock(100, TimeUnit.MILLISECONDS)) {
    try {
        // Try to acquire lock2
    } finally {
        lock1.unlock();
    }
}

3. Use higher-level concurrency APIs

Classes like BlockingQueue, ExecutorService, ForkJoinPool abstract away low-level locking.

4. Deadlock detection tools

Use jconsole, jstack, or profilers to identify thread dumps and blocked threads.

5. Avoid nested locks

Minimize use of multiple synchronized blocks where possible.


📌 What's New in Java Concurrency (8–21)

  • Java 8: CompletableFuture, lambda-friendly parallel APIs
  • Java 9: Flow API for reactive streams
  • Java 11: Minor enhancements to concurrent collections
  • Java 21: Structured concurrency, virtual threads, ScopedValue for safer thread context

✅ Best Practices

  • Define and follow a strict lock acquisition order.
  • Use non-blocking algorithms when available.
  • Avoid unnecessary locking.
  • Release locks promptly inside finally blocks.
  • Use modern abstractions (Executors, BlockingQueue) to reduce low-level locking needs.

❓ FAQ

  1. How can I detect a deadlock in Java?
    Use jstack or jconsole to inspect thread dumps for cycles.

  2. Is synchronized more prone to deadlock than ReentrantLock?
    Both can cause deadlocks if not used carefully.

  3. What’s the difference between starvation and deadlock?
    Starvation is indefinite delay; deadlock is complete halt.

  4. Can wait()/notify() lead to deadlocks?
    Yes, if combined with nested synchronized blocks improperly.

  5. Do deadlocks happen only on objects?
    They can occur with any kind of resource — files, DB connections, etc.

  6. Can deadlocks occur with only one lock?
    No — at least two resources are required for a circular wait.

  7. Does Java automatically resolve deadlocks?
    No — developers must prevent or detect them.

  8. Can virtual threads still deadlock?
    Yes — deadlocks are a logical error, not a thread model issue.

  9. What is livelock?
    Threads keep changing state but make no progress — different from deadlock.

  10. Should I always use tryLock()?
    Use it when you can’t guarantee lock order or want to fail fast.


🧾 Conclusion and Key Takeaways

  • Deadlocks freeze your application and must be avoided proactively.
  • Understand the four conditions that cause deadlock and break at least one.
  • Use consistent locking order, timeouts, and avoid nested locks.
  • Leverage modern APIs and tools to design deadlock-free concurrent code.

Understanding deadlocks helps you build stable, scalable, and bug-free Java multithreaded applications.