JavaMultithreadingadvanced
Updated:

Deadlock in Java: Causes, Detection and Prevention

6 min read

Deadlock freezes threads forever without throwing an exception. Learn the four conditions that cause it, how to detect it with jstack, and how to prevent it.

TL;DR – Quick Answer

A deadlock in Java happens when two or more threads wait for each other's locks forever, so none of them can proceed. It needs four conditions to occur: mutual exclusion, hold and wait, no preemption, and circular wait. You detect it with thread dumps (jstack) or ThreadMXBean, and prevent it mainly by acquiring locks in a fixed global order or using tryLock with a timeout.

On This Page

A deadlock is the worst kind of concurrency bug: your application does not crash, does not log an error, and does not throw an exception. It just stops. Two threads sit in the BLOCKED state forever, each holding a lock the other one needs, and every request that touches those locks piles up behind them.

If you already understand synchronization in Java, this page takes you one level deeper: why deadlocks happen, how to reproduce one in 30 lines of code, how to read the evidence in a thread dump, and the specific coding rules that make deadlock impossible.

What exactly is a deadlock?

A deadlock occurs when two or more threads are blocked forever, each waiting for a lock held by another thread in the group. The simplest version involves two threads and two locks:

  • Thread A acquires lock 1, then tries to acquire lock 2.
  • Thread B acquires lock 2, then tries to acquire lock 1.

If both threads complete their first acquisition before either attempts the second, neither can ever proceed. Thread A waits on lock 2 (held by B), and B waits on lock 1 (held by A). The JVM will happily let them wait until the process is killed.

Note the timing dependency: the same code can run cleanly a thousand times and deadlock on run 1,001. That is what makes deadlocks so dangerous in production — they hide during testing because the unlucky interleaving is rare.

The four Coffman conditions

A deadlock can only happen when all four of these conditions hold at the same time. Interviewers love this list because it maps directly to prevention strategies.

Condition Meaning How to break it
Mutual exclusion Only one thread can hold the lock at a time Use lock-free structures or immutable data
Hold and wait A thread holds one lock while waiting for another Acquire all locks at once, or none
No preemption A lock cannot be forcibly taken from a thread Use tryLock with a timeout and back off
Circular wait A cycle exists: A waits for B, B waits for A Impose a global lock-acquisition order

Breaking any one condition prevents deadlock. In practice, the two techniques you will actually use are breaking circular wait (lock ordering) and breaking no-preemption (tryLock with timeout). The other two are usually impractical for shared mutable state.

A runnable deadlock example

Run this class and it will hang almost every time. Notice the opposite lock order in the two threads — that is the entire bug.

public class DeadlockDemo {

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

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (LOCK_A) {
                System.out.println("T1 holds A, wants B");
                sleep(100); // widen the deadlock window
                synchronized (LOCK_B) {
                    System.out.println("T1 got both");
                }
            }
        }, "worker-1");

        Thread t2 = new Thread(() -> {
            synchronized (LOCK_B) {
                System.out.println("T2 holds B, wants A");
                sleep(100);
                synchronized (LOCK_A) {
                    System.out.println("T2 got both");
                }
            }
        }, "worker-2");

        t1.start();
        t2.start();
    }

    private static void sleep(long ms) {
        try { Thread.sleep(ms); } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

You will see both "holds" messages print, and then nothing. Neither "got both" line ever appears. Check the thread lifecycle page and you will recognise the state: both threads are BLOCKED, not WAITING — they are stuck on monitor entry.

Common mistake: A common mistake beginners make is thinking Thread.sleep() causes the deadlock here. Sleep only widens the timing window so the demo fails reliably. The real bug is the inconsistent lock order, and it can deadlock even without any sleep call.

How to detect a deadlock

Thread dumps with jstack

The fastest diagnostic is a thread dump. Find the process ID with jps, then run jstack <pid>. When a monitor deadlock exists, the JVM prints an explicit analysis at the bottom of the dump:

Found one Java-level deadlock:
=============================
"worker-2":
  waiting to lock monitor 0x... (object 0x..., a java.lang.Object),
  which is held by "worker-1"
"worker-1":
  waiting to lock monitor 0x... (object 0x..., a java.lang.Object),
  which is held by "worker-2"

The dump names each thread, the lock it wants, and the thread that owns it. Reading this section is a standard task in senior Java interview rounds, so practise it on the demo above.

Programmatic detection with ThreadMXBean

You can also detect deadlocks from inside the application — useful for a health-check endpoint that flags a wedged service:

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;

public class DeadlockWatchdog {

    public static void main(String[] args) throws InterruptedException {
        // start DeadlockDemo threads here, then:
        Thread.sleep(500);

        ThreadMXBean bean = ManagementFactory.getThreadMXBean();
        long[] ids = bean.findDeadlockedThreads(); // null when none

        if (ids != null) {
            for (ThreadInfo info : bean.getThreadInfo(ids)) {
                System.out.printf("%s is blocked on %s, held by %s%n",
                        info.getThreadName(),
                        info.getLockName(),
                        info.getLockOwnerName());
            }
        }
    }
}

findDeadlockedThreads() covers both synchronized monitors and java.util.concurrent locks such as ReentrantLock. The older findMonitorDeadlockedThreads() only sees monitors.

Pro tip: Detection tells you a deadlock exists — it cannot fix it. The JVM never preempts a lock, so the only remediation for a live deadlock is restarting the process. Treat detection as an alerting tool and put your real effort into prevention.

How to prevent deadlocks

Strategy 1: consistent lock ordering

The single most effective rule: every thread acquires locks in the same global order. If all code takes LOCK_A before LOCK_B, a circular wait is impossible.

When locks belong to dynamic objects (say, transferring money between two accounts), derive the order from a stable property such as an ID:

public class SafeTransfer {

    public void transfer(Account from, Account to, long amount) {
        Account first = from.getId() < to.getId() ? from : to;
        Account second = from.getId() < to.getId() ? to : from;

        synchronized (first) {
            synchronized (second) {
                from.debit(amount);
                to.credit(amount);
            }
        }
    }
}

Whichever direction the transfer runs, both threads lock the lower-ID account first, so the circular wait condition is broken.

Strategy 2: tryLock with timeout

ReentrantLock.tryLock(timeout, unit) breaks the no-preemption condition: if the second lock is not available in time, the thread releases what it holds and retries or fails cleanly instead of blocking forever.

if (lock1.tryLock(50, TimeUnit.MILLISECONDS)) {
    try {
        if (lock2.tryLock(50, TimeUnit.MILLISECONDS)) {
            try {
                doWork();
            } finally { lock2.unlock(); }
        }
    } finally { lock1.unlock(); }
}

Strategy 3: reduce locking altogether

  • Keep synchronized blocks small and never call unknown (alien) code while holding a lock.
  • Prefer concurrent collections — see HashMap vs ConcurrentHashMap — over manually locked maps.
  • Use immutable objects and thread confinement so many code paths need no lock at all.
  • Prefer higher-level constructs from java.util.concurrent (executors, queues) over raw locks; the basics are covered in multithreading in Java.

Pro tip: A single lock cannot deadlock with itself. Before adding a second lock to a class, ask whether one coarser lock is fast enough. Correct-but-slightly-slower beats fast-but-occasionally-frozen in almost every business application.

Deadlock vs livelock vs starvation

These three are frequently confused and frequently asked together.

Problem Thread state CPU usage Example
Deadlock BLOCKED forever None Two threads, opposite lock order
Livelock RUNNABLE, no progress High Two threads endlessly retrying and backing off in sync
Starvation Waiting indefinitely Normal Low-priority thread never scheduled; unfair lock always won by others

A livelock often appears when you fix a deadlock badly: both threads detect contention, release, retry at the same moment, collide again, and loop. Adding a small random backoff before retrying usually resolves it. Starvation is addressed with fair locks (new ReentrantLock(true)) or by not abusing thread priorities.

How this appears in interviews

Deadlock questions show up from the 2-year experience level onward, usually in this sequence: define it, list the four conditions, write or spot a deadlock in code, then explain prevention. The strongest answers name lock ordering first and mention tryLock as the fallback, then volunteer how to confirm a deadlock with jstack.

Interview note: After you explain prevention, many interviewers follow up with: "Your production service is frozen right now — what do you do?" The expected answer is operational: capture a thread dump (or two, a few seconds apart) before restarting, confirm the Found one Java-level deadlock section, identify the two code paths, and fix the lock order. Saying "restart the server" without capturing evidence is the trap.

Deadlock is a core topic in the concurrency module of our Java Full Stack course, where you debug a live deadlock with jstack in a lab session rather than just reading about it.

Frequently Asked Questions

What is a deadlock in Java in simple terms?
A deadlock is a situation where two or more threads block forever because each one holds a lock the other needs. Thread A holds lock 1 and waits for lock 2, while thread B holds lock 2 and waits for lock 1. Neither can move, and the JVM does not throw any exception to warn you.
What are the four conditions required for a deadlock?
The four Coffman conditions are mutual exclusion (a resource can be held by only one thread), hold and wait (a thread holds one lock while waiting for another), no preemption (locks cannot be forcibly taken away), and circular wait (a cycle of threads each waiting for the next one's lock). Breaking any one of these prevents deadlock.
How do I detect a deadlock in a running Java application?
Take a thread dump with jstack <pid> or press Ctrl+Break on Windows. The JVM prints a 'Found one Java-level deadlock' section showing which threads hold and wait for which monitors. Programmatically, ThreadMXBean.findDeadlockedThreads() returns the IDs of deadlocked threads and works for both synchronized blocks and ReentrantLock.
Does the JVM automatically recover from a deadlock?
No. The JVM can detect and report a deadlock in a thread dump, but it never breaks one. Deadlocked threads stay blocked until the process is restarted, which is why prevention through lock ordering is far more important than detection.
What is the difference between deadlock and livelock?
In a deadlock, threads are stuck in the BLOCKED state doing nothing. In a livelock, threads keep running and responding to each other but still make no progress, like two people repeatedly stepping aside in a corridor. Livelock burns CPU while deadlock does not.
Can a single thread deadlock itself in Java?
Not with synchronized or ReentrantLock, because both are reentrant: a thread can re-acquire a lock it already owns. However, a thread can deadlock itself with non-reentrant constructs, for example by calling lock() twice on the same Semaphore permit or by blocking on a Future that its own thread pool has no free worker to complete.

Want to Build Your Career in Java Full Stack with AI?

Join CodeBegun and train with working industry engineers — Explore the Program

Apply for Demo Class →
Siva Prasad Galaba
Founder, CodeBegun · Staff Engineer

Founder of CodeBegun. 15+ years building Java systems at companies like Crunchyroll. Teaches Java, Spring Boot and system design the way the industry actually works, and mentors students through projects, mock interviews and placement preparation.

Technically reviewed by CodeBegun Technical TeamLast reviewed 14 July 2026 LinkedIn
Chat with us