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
- Mutual exclusion – Resources can’t be shared.
- Hold and wait – Threads hold one resource and wait for another.
- No preemption – Resources can’t be forcibly taken away.
- 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
-
How can I detect a deadlock in Java?
Usejstack
orjconsole
to inspect thread dumps for cycles. -
Is
synchronized
more prone to deadlock thanReentrantLock
?
Both can cause deadlocks if not used carefully. -
What’s the difference between starvation and deadlock?
Starvation is indefinite delay; deadlock is complete halt. -
Can
wait()
/notify()
lead to deadlocks?
Yes, if combined with nested synchronized blocks improperly. -
Do deadlocks happen only on objects?
They can occur with any kind of resource — files, DB connections, etc. -
Can deadlocks occur with only one lock?
No — at least two resources are required for a circular wait. -
Does Java automatically resolve deadlocks?
No — developers must prevent or detect them. -
Can virtual threads still deadlock?
Yes — deadlocks are a logical error, not a thread model issue. -
What is livelock?
Threads keep changing state but make no progress — different from deadlock. -
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.