Deadlock in Java is a condition where two or more threads are blocked forever, each waiting for the other to release a lock. This occurs primarily in a multithreaded environment when synchronized resources are used in such a way that a circular wait condition arises. Understanding and preventing deadlocks is crucial for building efficient and bug-free Java applications.
🔁 What Causes Deadlock in Java?
Deadlock occurs when the following four conditions are met simultaneously:
- Mutual Exclusion: At least one resource must be held in a non-shareable mode.
- Hold and Wait: A thread holding at least one resource is waiting to acquire additional resources held by other threads.
- No Preemption: Resources cannot be forcibly removed from threads holding them.
- Circular Wait: A set of threads are each waiting for a resource that the next thread in the cycle holds.
Note: All four conditions must be true for a deadlock to occur. Removing any one can prevent deadlock.
🧵 Java Deadlock Code Example
Let’s look at a simple code example that demonstrates a classic deadlock scenario:
class SharedResource {
void methodA() {
synchronized (String.class) {
System.out.println(Thread.currentThread().getName() + " locked String.class");
synchronized (Integer.class) {
System.out.println(Thread.currentThread().getName() + " locked Integer.class");
}
}
}
void methodB() {
synchronized (Integer.class) {
System.out.println(Thread.currentThread().getName() + " locked Integer.class");
synchronized (String.class) {
System.out.println(Thread.currentThread().getName() + " locked String.class");
}
}
}
}
public class DeadlockExample {
public static void main(String[] args) {
SharedResource resource = new SharedResource();
Thread t1 = new Thread(() -> resource.methodA(), "Thread-1");
Thread t2 = new Thread(() -> resource.methodB(), "Thread-2");
t1.start();
t2.start();
}
}
In the code above:
Thread-1
locksString.class
and waits forInteger.class
.Thread-2
locksInteger.class
and waits forString.class
.
This creates a circular wait and results in a deadlock.
⚠️ How to Detect Deadlocks in Java?
Deadlocks can be hard to detect manually. Java provides several tools:
- jconsole – A Java monitoring tool.
- jstack – Prints Java thread stack traces for a Java process.
- VisualVM – Graphical interface to monitor JVM.
Using jstack:
jstack <pid> | grep -A 30 "Found one Java-level deadlock"
This command identifies deadlocked threads and shows the locks they are holding or waiting for.
💡 How to Prevent Deadlocks in Java?
Several techniques can help prevent deadlocks in Java:
1. Lock Ordering
Always acquire locks in a consistent global order.
synchronized (String.class) {
synchronized (Integer.class) {
// do something
}
}
Ensure that all threads follow this same lock order.
2. Try-Lock with Timeout
Use ReentrantLock.tryLock()
with timeout to avoid indefinite waiting.
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;
public class TryLockExample {
private static ReentrantLock lock1 = new ReentrantLock();
private static ReentrantLock lock2 = new ReentrantLock();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
try {
if (lock1.tryLock(1, TimeUnit.SECONDS)) {
Thread.sleep(500);
if (lock2.tryLock(1, TimeUnit.SECONDS)) {
System.out.println("Thread 1 acquired both locks");
lock2.unlock();
}
lock1.unlock();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread t2 = new Thread(() -> {
try {
if (lock2.tryLock(1, TimeUnit.SECONDS)) {
Thread.sleep(500);
if (lock1.tryLock(1, TimeUnit.SECONDS)) {
System.out.println("Thread 2 acquired both locks");
lock1.unlock();
}
lock2.unlock();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t1.start();
t2.start();
}
}
This technique prevents deadlock by avoiding waiting forever for a lock.
3. Avoid Nested Locks
Minimize the use of nested synchronized blocks. If not necessary, don’t acquire multiple locks simultaneously.
4. Use Higher-Level Concurrency Utilities
Prefer classes from java.util.concurrent
like Semaphore
, ExecutorService
, etc., which provide better control and abstraction.
📊 Real-World Scenario Example
Let’s simulate a banking scenario where deadlock may occur if accounts try to transfer money simultaneously:
class Account {
private int balance = 1000;
public void withdraw(int amount) {
balance -= amount;
}
public void deposit(int amount) {
balance += amount;
}
public int getBalance() {
return balance;
}
}
class Transfer {
public void transferMoney(Account from, Account to, int amount) {
synchronized (from) {
synchronized (to) {
from.withdraw(amount);
to.deposit(amount);
}
}
}
}
public class BankDeadlock {
public static void main(String[] args) {
Account acc1 = new Account();
Account acc2 = new Account();
Transfer transfer = new Transfer();
Thread t1 = new Thread(() -> transfer.transferMoney(acc1, acc2, 100));
Thread t2 = new Thread(() -> transfer.transferMoney(acc2, acc1, 200));
t1.start();
t2.start();
}
}
This program might deadlock because t