For working professionals
For fresh graduates
More
6. JDK in Java
7. C++ Vs Java
16. Java If-else
18. Loops in Java
20. For Loop in Java
45. Packages in Java
52. Java Collection
55. Generics In Java
56. Java Interfaces
59. Streams in Java
62. Thread in Java
66. Deadlock in Java
73. Applet in Java
74. Java Swing
75. Java Frameworks
77. JUnit Testing
80. Jar file in Java
81. Java Clean Code
85. Java 8 features
86. String in Java
92. HashMap in Java
97. Enum in Java
100. Hashcode in Java
104. Linked List in Java
108. Array Length in Java
110. Split in java
111. Map In Java
114. HashSet in Java
117. DateFormat in Java
120. Java List Size
121. Java APIs
127. Identifiers in Java
129. Set in Java
131. Try Catch in Java
132. Bubble Sort in Java
134. Queue in Java
141. Jagged Array in Java
143. Java String Format
144. Replace in Java
145. charAt() in Java
146. CompareTo in Java
150. parseInt in Java
152. Abstraction in Java
153. String Input in Java
155. instanceof in Java
156. Math Floor in Java
157. Selection Sort Java
158. int to char in Java
163. Deque in Java
171. Trim in Java
172. RxJava
173. Recursion in Java
174. HashSet Java
176. Square Root in Java
189. Javafx
Deadlock in Java Programming occurs when two or more threads are blocked forever, each waiting for a resource that another thread holds. This critical concurrency issue can completely halt your application's execution with no automatic recovery mechanism. Understanding deadlock in Java multithreading is essential for developing concurrent applications. In this guide, we'll examine what causes deadlocks, explore deadlock examples in Java, and discuss effective strategies for how to avoid deadlock in Java.
Step up your Java skills! Join upGrad’s software engineering course for hands-on learning and professional guidance.
A deadlock happens when multiple threads are unable to proceed because each is waiting for resources held by another thread in the same waiting group. This creates a circular dependency where no thread can continue execution, resulting in a permanently blocked state.
Also read: Thread in Java
For a deadlock to occur, the following four conditions (known as Coffman conditions) must be present simultaneously:
Step into the future of tech with these top-rated AI and data science courses:
• Master’s in Data Science – LJMU
• PG Diploma in ML & AI – IIIT Bangalore
Thread deadlock in Java commonly occurs in the following scenarios:
When multiple threads acquire locks in different orders, they can create a situation where each thread holds a lock that another thread needs to proceed.
When multiple transactions lock rows in different orders, database-level deadlocks can occur in JDBC operations.
Improper use of Thread.join() method can create deadlocks if threads wait for each other in a circular manner.
When threads in a resource pool are waiting for resources that will never become available.
Must explore: Comprehensive Guide to Exception Handling in Java: Best Practices and Examples
Let's examine a classic deadlock example in Java where two threads hold one resource each and wait for the other resource:
public class DeadlockExample {
// Resource objects
private static final Object RESOURCE_1 = new Object();
private static final Object RESOURCE_2 = new Object();
public static void main(String[] args) {
// First thread - acquires RESOURCE_1, then tries to acquire RESOURCE_2
Thread thread1 = new Thread(() -> {
synchronized (RESOURCE_1) {
System.out.println("Thread 1: Holding RESOURCE_1...");
try {
// Adding delay to ensure the deadlock occurs
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 1: Waiting for RESOURCE_2...");
synchronized (RESOURCE_2) {
System.out.println("Thread 1: Holding RESOURCE_1 and RESOURCE_2");
}
}
});
// Second thread - acquires RESOURCE_2, then tries to acquire RESOURCE_1
Thread thread2 = new Thread(() -> {
synchronized (RESOURCE_2) {
System.out.println("Thread 2: Holding RESOURCE_2...");
try {
// Adding delay to ensure the deadlock occurs
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 2: Waiting for RESOURCE_1...");
synchronized (RESOURCE_1) {
System.out.println("Thread 2: Holding RESOURCE_2 and RESOURCE_1");
}
}
});
// Start both threads
thread1.start();
thread2.start();
}
}
Expected Output:
Thread 1: Holding RESOURCE_1...
Thread 2: Holding RESOURCE_2...
Thread 1: Waiting for RESOURCE_2...
Thread 2: Waiting for RESOURCE_1...
Explanation: In this example, the program will hang indefinitely because:
Also explore: Thread Lifecycle In Java
Detecting deadlocks is a crucial aspect of multithreaded application development. Here are some techniques for deadlock detection:
Java provides built-in tools to detect deadlocks:
// Example using ThreadMXBean to detect deadlocks
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;
public class DeadlockDetector {
public static void checkForDeadlocks() {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
long[] threadIds = threadMXBean.findDeadlockedThreads();
if (threadIds != null) {
System.out.println("Deadlock detected!");
// Get more info about the deadlocked threads if needed
threadMXBean.getThreadInfo(threadIds).forEach(System.out::println);
}
}
}
Expected Output (when deadlock occurs):
Deadlock detected!
[Thread details would be displayed here]
Explanation: This code uses the ThreadMXBean from the java.lang.management package to detect deadlocked threads. The findDeadlockedThreads() method returns an array of thread IDs that are deadlocked, or null if no deadlock is detected.
Check out: Ultimate Guide to Synchronization in Java
Tools like JConsole, VisualVM, and Java Mission Control can visualize thread states and detect deadlocks in running applications.
Taking thread dumps using jstack utility and analyzing them:
jstack <process-id> > thread-dump.txt
Also read: Difference between Multithreading and Multitasking in Java
Preventing deadlocks is better than trying to recover from them. Here are some effective strategies:
Always acquire locks in a consistent, predetermined order:
public class DeadlockPrevention {
private static final Object RESOURCE_1 = new Object();
private static final Object RESOURCE_2 = new Object();
public static void main(String[] args) {
// Both threads acquire locks in the same order: RESOURCE_1, then RESOURCE_2
Thread thread1 = new Thread(() -> {
synchronized (RESOURCE_1) {
System.out.println("Thread 1: Holding RESOURCE_1...");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (RESOURCE_2) {
System.out.println("Thread 1: Holding RESOURCE_1 and RESOURCE_2");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (RESOURCE_1) { // Note: Both threads now acquire RESOURCE_1 first
System.out.println("Thread 2: Holding RESOURCE_1...");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (RESOURCE_2) {
System.out.println("Thread 2: Holding RESOURCE_1 and RESOURCE_2");
}
}
});
thread1.start();
thread2.start();
}
}
Expected Output:
Thread 1: Holding RESOURCE_1...
Thread 1: Holding RESOURCE_1 and RESOURCE_2
Thread 2: Holding RESOURCE_1...
Thread 2: Holding RESOURCE_1 and RESOURCE_2
Explanation: In this improved version, both threads acquire resources in the same order (RESOURCE_1, then RESOURCE_2), which prevents the circular wait condition necessary for a deadlock.
Also read: Java Interfaces
Use timed lock attempts instead of indefinite waits:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;
public class LockTimeoutExample {
private static final Lock LOCK_1 = new ReentrantLock();
private static final Lock LOCK_2 = new ReentrantLock();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
boolean acquired1 = false;
boolean acquired2 = false;
try {
acquired1 = LOCK_1.tryLock(1, TimeUnit.SECONDS);
if (acquired1) {
System.out.println("Thread 1: Acquired LOCK_1");
// Try to get the second lock
acquired2 = LOCK_2.tryLock(1, TimeUnit.SECONDS);
if (acquired2) {
System.out.println("Thread 1: Acquired LOCK_2");
// Do work here
} else {
System.out.println("Thread 1: Failed to acquire LOCK_2. Releasing LOCK_1");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// Release locks in reverse order of acquisition
if (acquired2) LOCK_2.unlock();
if (acquired1) LOCK_1.unlock();
}
});
// Similar logic for thread2
thread1.start();
}
}
Expected Output:
Thread 1: Acquired LOCK_1
Thread 1: Acquired LOCK_2
or
Thread 1: Acquired LOCK_1
Thread 1: Failed to acquire LOCK_2. Releasing LOCK_1
Explanation: Using tryLock() with a timeout prevents indefinite waiting by allowing the thread to give up after a specified time if it cannot acquire the lock.
Java's concurrent collections are designed to prevent deadlocks:
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;
public class ConcurrentCollectionExample {
public static void main(String[] args) {
// Thread-safe map that doesn't require external synchronization
Map<String, String> concurrentMap = new ConcurrentHashMap<>();
Thread thread1 = new Thread(() -> {
concurrentMap.put("key1", "value1");
System.out.println("Thread 1: " + concurrentMap.get("key2"));
});
Thread thread2 = new Thread(() -> {
concurrentMap.put("key2", "value2");
System.out.println("Thread 2: " + concurrentMap.get("key1"));
});
thread1.start();
thread2.start();
}
}
Expected Output:
Thread 1: value2
Thread 2: value1
or
Thread 2: value1
Thread 1: value2
Explanation: ConcurrentHashMap allows multiple threads to read and write concurrently without external synchronization, eliminating a common source of deadlocks.
When using Thread.join(), include a timeout:
public class ThreadJoinTimeout {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
try {
System.out.println("Thread 1 is running");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread1.start();
try {
// Wait for thread1 to complete, but with a timeout
thread1.join(3000); // Wait for max 3 seconds
if (thread1.isAlive()) {
System.out.println("Thread 1 is still running after timeout");
} else {
System.out.println("Thread 1 completed within the timeout");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Expected Output:
Thread 1 is running
Thread 1 completed within the timeout
Explanation: Using join() with a timeout ensures that the main thread won't wait indefinitely if the other thread gets stuck.
In some scenarios, it's possible to implement deadlock recovery mechanisms:
import java.util.concurrent.atomic.AtomicInteger;
public class DeadlockRecovery {
private static final Object RESOURCE_A = new Object();
private static final Object RESOURCE_B = new Object();
private static final AtomicInteger attempts = new AtomicInteger(0);
public static void main(String[] args) {
Thread watchdog = new Thread(() -> {
while (true) {
try {
Thread.sleep(5000); // Check every 5 seconds
// If multiple attempts have been made, assume deadlock
if (attempts.get() > 3) {
System.out.println("Potential deadlock detected! Interrupting threads...");
// Find and interrupt deadlocked threads
Thread.getAllStackTraces().keySet().forEach(thread -> {
if (thread.getState() == Thread.State.BLOCKED) {
System.out.println("Interrupting blocked thread: " + thread.getName());
thread.interrupt();
}
});
attempts.set(0);
}
} catch (InterruptedException e) {
break;
}
}
});
watchdog.setDaemon(true);
watchdog.start();
// Your potentially deadlocking code here
}
}
Expected Output:
Potential deadlock detected! Interrupting threads...
Interrupting blocked thread: Thread-1
Interrupting blocked thread: Thread-2
Explanation: This watchdog thread monitors for potential deadlocks by tracking the number of attempts to acquire resources and interrupts threads that appear to be deadlocked.
Organize resources in a hierarchy and always acquire them in hierarchical order:
class Account {
private final int id;
private double balance;
public Account(int id, double initialBalance) {
this.id = id;
this.balance = initialBalance;
}
public int getId() {
return id;
}
public void withdraw(double amount) {
balance -= amount;
}
public void deposit(double amount) {
balance += amount;
}
}
public class HierarchicalLocking {
public static void transfer(Account fromAccount, Account toAccount, double amount) {
// Always lock accounts in order of their ID to prevent deadlocks
Account firstLock = fromAccount.getId() < toAccount.getId() ? fromAccount : toAccount;
Account secondLock = fromAccount.getId() < toAccount.getId() ? toAccount : fromAccount;
synchronized (firstLock) {
System.out.println("Acquired lock on account: " + firstLock.getId());
synchronized (secondLock) {
System.out.println("Acquired lock on account: " + secondLock.getId());
// Perform the actual transfer
if (fromAccount == firstLock) {
fromAccount.withdraw(amount);
toAccount.deposit(amount);
} else {
toAccount.deposit(amount);
fromAccount.withdraw(amount);
}
System.out.println("Transfer complete");
}
}
}
public static void main(String[] args) {
final Account account1 = new Account(1, 1000);
final Account account2 = new Account(2, 2000);
// Both threads will acquire locks in the same order (account1 then account2)
// regardless of which is the source/destination account
new Thread(() -> transfer(account1, account2, 100)).start();
new Thread(() -> transfer(account2, account1, 200)).start();
}
}
Expected Output:
Acquired lock on account: 1
Acquired lock on account: 2
Transfer complete
Acquired lock on account: 1
Acquired lock on account: 2
Transfer complete
Explanation: This implementation ensures that locks are always acquired in a consistent order (by account ID) regardless of the transfer direction, preventing deadlocks.
Deadlock in Java remains one of the most challenging aspects of concurrent programming. By understanding the conditions that cause deadlocks and implementing proper prevention techniques such as consistent lock ordering, timeouts, and resource hierarchies, you can develop more robust multithreaded applications.
Remember that proactive prevention is far more effective than trying to detect and recover from deadlocks after they occur. Regularly testing your application under high concurrency conditions will help identify potential deadlock scenarios before they impact your production environment.
Deadlock in Java primarily occurs when two or more threads each hold resources that the other needs and neither is willing to release their held resources. The circular wait condition, combined with mutual exclusion, hold-and-wait, and no preemption, creates a situation where threads are perpetually waiting for each other.
You can identify deadlocks in a running Java application using tools like jstack to generate thread dumps, JConsole or VisualVM for visual monitoring, or programmatically using ThreadMXBean's findDeadlockedThreads() method to detect deadlocked threads.
No, deadlocks are specific to multi-threaded environments. Single-threaded applications execute sequentially, so there's no possibility of threads waiting for each other's resources in a circular manner.
Deadlock occurs when threads are permanently blocked waiting for resources held by each other. Starvation occurs when a thread is unable to gain regular access to shared resources, preventing progress, but the system as a whole continues functioning.
The JVM can detect deadlocks but doesn't automatically resolve them. Detection is possible through ThreadMXBean or thread dumps, but resolution must be programmatically implemented by the developer.
The four necessary conditions are: (1) Mutual Exclusion - resources cannot be shared simultaneously, (2) Hold and Wait - threads hold resources while waiting for others, (3) No Preemption - resources cannot be forcibly taken away, and (4) Circular Wait - a circular chain of waiting threads exists.
Lock timeouts limit how long a thread will wait to acquire a lock. If the timeout expires, the thread can release any held locks and retry later, potentially breaking the hold-and-wait condition necessary for deadlocks.
Yes, improper use of synchronized blocks or methods is one of the most common causes of deadlocks in Java, particularly when multiple locks are acquired in different orders by different threads.
The concurrent package locks offer more sophisticated features like tryLock() with timeouts and interruptible lock acquisition, which can help prevent or recover from potential deadlock situations, unlike synchronized which blocks indefinitely.
Livelock occurs when threads continuously change their state in response to each other without making progress. Unlike deadlock where threads are blocked, in livelock, threads are actively performing actions but cannot complete their task.
To prevent database deadlocks: (1) Keep transactions short, (2) Access tables in a consistent order, (3) Use appropriate isolation levels, (4) Add deadlock detection/retry logic, and (5) Consider using optimistic locking where appropriate
Take the Free Quiz on Java
Answer quick questions and assess your Java knowledge
Author
Talk to our experts. We are available 7 days a week, 9 AM to 12 AM (midnight)
Indian Nationals
1800 210 2020
Foreign Nationals
+918068792934
1.The above statistics depend on various factors and individual results may vary. Past performance is no guarantee of future results.
2.The student assumes full responsibility for all expenses associated with visas, travel, & related costs. upGrad does not provide any a.