💡 What is a Deadlock in Java?
A deadlock occurs when two or more threads are waiting for each other’s resources (locks), and none of them can proceed.
In other words, each thread holds one lock and waits for another, leading to a permanent waiting state — a deadlock.
Deadlocks are one of the most common multithreading pitfalls in Java and are often used in interviews to test understanding of synchronization and resource contention.
⚙️ Scenario Overview
Let’s consider a simple scenario with two locks (lock1 and lock2) and two threads (Thread1 and Thread2):
Thread1tries to acquirelock1first and thenlock2.Thread2tries to acquirelock2first and thenlock1.
If Thread1 holds lock1 and Thread2 holds lock2, both threads wait indefinitely for each other to release the other lock.
This is a classic deadlock.
🧩 Code Example — Java Thread Deadlock
public class MyClass {
public static void main(String args[]) {
// Create two locks (objects)
String lock1 = "lock1";
String lock2 = "lock2";
// Task 1 tries to acquire lock1 -> lock2
Runnable task1 = new Runnable() {
public void run() {
synchronized (lock1) {
System.out.println("Thread1: Main critical section executed");
synchronized (lock2) {
System.out.println("Thread1: Sub critical section executed");
try {
lock2.notify(); // Wake up thread waiting on lock2
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
};
// Task 2 tries to acquire lock2 -> lock1
Runnable task2 = new Runnable() {
public void run() {
synchronized (lock2) {
System.out.println("Thread2: Main critical section executed");
try {
lock2.wait(); // Release lock2 and wait
} catch (Exception e) {
e.printStackTrace();
}
synchronized (lock1) {
System.out.println("Thread2: Sub critical section executed");
}
}
}
};
// Create and start threads
Thread t1 = new Thread(task1);
Thread t2 = new Thread(task2);
t1.start();
t2.start();
}
}
🔍 Step-by-Step Explanation
- Two locks (
lock1andlock2) are created as shared resources. Thread1acquires lock1 and tries to get lock2.Thread2acquires lock2 and tries to get lock1.- Both threads now wait forever, each holding one lock and waiting for the other.
This creates a deadlock, where neither thread can proceed, because both are waiting indefinitely.
🧠 Key Concepts Demonstrated
1. Synchronized Blocks
Each thread attempts to acquire an object’s monitor lock before entering a synchronized block.
2. Wait and Notify
wait()releases the lock temporarily and moves the thread to the waiting state.notify()wakes up a thread that’s waiting on the same object.
⚠️ Note: In the above code,
wait()andnotify()are not properly balanced across threads.
This is intentional — it demonstrates how wrong synchronization patterns can cause deadlocks.
3. Thread States in Deadlock
- Both threads move into a BLOCKED or WAITING state.
- The JVM thread dump (
jstack) would show both threads waiting to acquire locks held by each other.
🧩 How to Detect Deadlocks
Option 1: Using jstack
Run:
jstack <process_id>
You’ll see threads marked as BLOCKED with lines like:
Found one Java-level deadlock:
"Thread-1": waiting to lock monitor 0x00007f...
Option 2: Using IDE tools
IntelliJ IDEA or Eclipse can detect and visualize thread deadlocks during debugging.
✅ How to Avoid Deadlocks
- Always acquire locks in the same order across all threads.
Example: If one thread locksA → B, no other thread should lockB → A. - Use
tryLock()(fromjava.util.concurrent.locks.Lock) with a timeout instead ofsynchronized. - Keep synchronized blocks small — only protect the necessary code.
- Avoid nested locks where possible.
🧾 Summary
| Concept | Description |
|---|---|
| Deadlock | Two or more threads waiting for each other indefinitely. |
| Cause | Circular lock dependency (Thread1 → lock1 → lock2, Thread2 → lock2 → lock1). |
| Detection | JVM tools like jstack or IDE thread monitors. |
| Prevention | Consistent lock ordering, using tryLock(), minimizing nested locks. |
💬 Final Thought
Deadlocks are not just theoretical problems — they can freeze your production application if not carefully designed.
Understanding lock ordering, synchronization, and concurrency primitives is key to writing safe, scalable multithreaded Java code.
