Deadlock in Java
A deadlock in Java is a situation where two or more threads are stuck forever, each waiting for a lock that another one holds. Navika holds the pens and waits for the paper; Srihitha holds the paper and waits for the pens; neither can continue, so neither ever lets go. The program doesn't crash; it simply freezes. In this lesson, you will create a deadlock on purpose, find it with a thread dump, detect it from code, and fix it with lock ordering.
The Four Conditions
A deadlock can happen only when all four of these are true. Breaking any one of them prevents it.
| Condition | Meaning |
|---|---|
| Mutual exclusion | A lock can be held by only one thread at a time |
| Hold and wait | A thread holds one lock while waiting for another |
| No preemption | A lock can't be taken away; only its holder releases it |
| Circular wait | Thread A waits for B's lock, and B waits for A's |
The Checkpoint: Creating, Finding and Fixing a Deadlock
Step 1: A Deliberate Deadlock
This program never finishes, so it is run in a terminal rather than by the example runner. The short pause makes sure both threads grab their first lock before trying for the second.
public class Deadlock {
static final Object pens = new Object();
static final Object paper = new Object();
static void pause() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public static void main(String[] args) {
new Thread(() -> {
synchronized (pens) {
System.out.println("Navika has the pens, wants the paper");
pause();
synchronized (paper) {
System.out.println("Navika draws");
}
}
}, "navika").start();
new Thread(() -> {
synchronized (paper) {
System.out.println("Srihitha has the paper, wants the pens");
pause();
synchronized (pens) {
System.out.println("Srihitha draws");
}
}
}, "srihitha").start();
}
}Navika has the pens, wants the paper
Srihitha has the paper, wants the pensThe program then hangs: neither "draws" line is ever printed.
Step 2: Capturing a Thread Dump with jstack
While it hangs, find its process ID with jps and ask the JVM for a thread dump with jstack:
jps
jstack <process-id>The end of the dump reports the deadlock directly (memory addresses shortened):
Found one Java-level deadlock:
=============================
"navika":
waiting to lock monitor 0x... (object 0x...ce0, a java.lang.Object),
which is held by "srihitha"
"srihitha":
waiting to lock monitor 0x... (object 0x...cd0, a java.lang.Object),
which is held by "navika"
Java stack information for the threads listed above:
===================================================
"navika":
at Deadlock.lambda$main$0(Deadlock.java:19)
- waiting to lock <0x...ce0> (a java.lang.Object)
- locked <0x...cd0> (a java.lang.Object)
"srihitha":
at Deadlock.lambda$main$1(Deadlock.java:29)
- waiting to lock <0x...cd0> (a java.lang.Object)
- locked <0x...ce0> (a java.lang.Object)
Found 1 deadlock.Explanation: The dump names each thread, the lock it holds ("locked"), the lock it waits for, and the exact source line (19 and 29, the inner synchronized lines). Good thread names, such as navika and srihitha, make the dump easy to read.
Step 3: Detecting a Deadlock from Code
The JVM can also report deadlocks to the program itself through ThreadMXBean. Here the threads are daemons, so the program can exit after reporting:
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.Arrays;
public class DetectDeadlock {
static final Object pens = new Object();
static final Object paper = new Object();
static Thread start(String name, Object first, Object second) {
Thread t = new Thread(() -> {
synchronized (first) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
return;
}
synchronized (second) {
System.out.println(name + " finished");
}
}
}, name);
t.setDaemon(true);
t.start();
return t;
}
public static void main(String[] args) throws InterruptedException {
start("navika", pens, paper);
start("srihitha", paper, pens);
Thread.sleep(500);
ThreadMXBean bean = ManagementFactory.getThreadMXBean();
long[] ids = bean.findDeadlockedThreads();
String[] names = Arrays.stream(bean.getThreadInfo(ids))
.map(ThreadInfo::getThreadName).sorted().toArray(String[]::new);
System.out.println("Deadlocked threads: " + Arrays.toString(names));
}
}Deadlocked threads: [navika, srihitha]Explanation: After half a second, findDeadlockedThreads() returns the IDs of the threads stuck in a lock cycle. Monitoring tools use this to raise an alarm.
Step 4: The Fix, Lock Ordering
The circular wait disappears if every thread takes the locks in the same order:
public class NoDeadlock {
static final Object pens = new Object();
static final Object paper = new Object();
static Thread draw(String name) {
Thread t = new Thread(() -> {
synchronized (pens) {
synchronized (paper) {
System.out.println(name + " draws");
}
}
}, name);
t.start();
return t;
}
public static void main(String[] args) throws InterruptedException {
Thread a = draw("Navika");
a.join();
Thread b = draw("Srihitha");
b.join();
System.out.println("No deadlock: both always take pens, then paper");
}
}Navika draws
Srihitha draws
No deadlock: both always take pens, then paperExplanation: Both threads now lock pens first and paper second. Whoever gets pens first will also get paper, so a cycle can never form. (The threads are joined one at a time here only to make the output order fixed; the fix works with them running together too.)
Other Ways to Avoid Deadlock
- Hold one lock at a time whenever possible.
- Use timeouts:
ReentrantLock.tryLock(timeout)gives up instead of waiting forever (covered later in the course). - Keep synchronized code short, and never call unknown code (such as listeners) while holding a lock.
Common Mistakes
Nested synchronized blocks in different orders in different methods. That's the classic deadlock recipe.
Calling another object's synchronized method while holding your own lock. It hides a lock order you didn't plan.
Restarting a frozen program without a thread dump. Take a jstack first; it shows the cause.
Interview Corner
What is a deadlock, and how can you prevent it?
A deadlock is a state in which two or more threads wait indefinitely for locks held by each other. It requires mutual exclusion, hold-and-wait, no preemption and a circular wait. The usual prevention is a global lock ordering so a cycle can't form; other techniques are holding only one lock at a time, using tryLock with timeouts, and avoiding calls to external code while holding locks. Deadlocks can be diagnosed with jstack thread dumps or ThreadMXBean.findDeadlockedThreads().
Practice
- Easy: Change
NoDeadlockto start both threads before joining them, and run it many times to confirm it never hangs. - Harder: Write
transfer(Account from, Account to, int amount)that locks both accounts without deadlocking, by always locking the account with the smaller ID first.
Key Takeaways
Was this tutorial helpful?