Tutorial Playlist
A deadlock in Java is crucial in concurrent programming. They can make a huge impact on the performance and stability of applications. In Java, a deadlock arises when two or more threads are stuck indefinitely, awaiting the release of resources held by each other. Java developers must understand deadlocks and possess knowledge of techniques to prevent and resolve them effectively.
This tutorial aims to provide a comprehensive understanding of deadlock in Java with examples, how to detect deadlock in Java, and ways to avoid them.
A deadlock is a scenario in concurrent programming where multiple threads are stuck waiting for each other, leading to a complete standstill. It happens when each thread holds a resource while waiting for another one held by a different thread. As a result, none of the threads can progress, causing the program's execution to halt.
Let us learn what is a deadlock in Java with example programs.
public class upGradTutorials {
private static final Object resource1 = new Object();
private static final Object resource2 = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (resource1) {
System.out.println("Thread 1 acquired resource1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (resource2) {
System.out.println("Thread 1 acquired resource2");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (resource2) {
System.out.println("Thread 2 acquired resource2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (resource1) {
System.out.println("Thread 2 acquired resource1");
}
}
});
thread1.start();
thread2.start();
}
}
In this example, two threads (thread1 and thread2) that attempt to acquire two different resources (resource1 and resource2) in a specific order. However, the order of acquisition of resources in each thread is different. This creates a potential for a deadlock situation.
When thread1 starts executing, it acquires resource1 and then pauses for a while using Thread.sleep(1000) to simulate some work being done. Meanwhile, thread2 starts executing and acquires resource2. Both threads are waiting for the other resource to be released, causing a deadlock.
If you run this program, you'll notice it gets stuck and does not terminate. This is because thread1 is holding onto resource1 and waiting for resource2, while thread2 is holding onto resource2 and waiting for resource1. Hence, both threads are deadlocked, unable to proceed further.
Here is a more complicated example of a deadlock in Java involving multiple threads and shared resources:
public class upGradTutorials {
private static final Object resource1 = new Object();
private static final Object resource2 = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (resource1) {
System.out.println("Thread 1 acquired resource1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (resource2) {
System.out.println("Thread 1 acquired resource2");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (resource2) {
System.out.println("Thread 2 acquired resource2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (resource1) {
System.out.println("Thread 2 acquired resource1");
}
}
});
Thread thread3 = new Thread(() -> {
synchronized (resource1) {
System.out.println("Thread 3 acquired resource1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (resource2) {
System.out.println("Thread 3 acquired resource2");
}
}
});
thread1.start();
thread2.start();
thread3.start();
}
}
In this example, three threads (thread1, thread2, and thread3) that attempt to acquire two different resources (resource1 and resource2) in a specific order. However, the order of acquisition of resources in each thread is different, leading to a deadlock situation.
When thread1 starts executing, it acquires resource1 and then pauses using Thread.sleep(1000). Meanwhile, thread2 starts executing and acquires resource2. Similarly, thread3 starts executing and acquires resource1. All three threads are waiting for the other resources to be released, causing a deadlock.
If you run this program, you'll notice it gets stuck and does not terminate. This is because thread1 is holding onto resource1 and waiting for resource2, thread2 is holding onto resource2 and waiting for resource1, and thread3 is holding onto resource1 and waiting for resource2. Hence, all three threads are deadlocked, unable to proceed further.
To detect deadlocks in Java, you can employ the following approaches:-
Deadlocks usually arise when the following four conditions coincide simultaneously:
This condition states that at least one resource must be exclusively allocated to one thread at a time. It means that when a thread uses a particular resource, other threads are prevented from concurrently accessing or using the same resource.
Hold and wait refers to a situation where a thread holds a resource while waiting for another resource currently held by a different thread. It introduces a dependency chain among the threads, where each thread requires a resource held by another thread.
If the threads do not release their held resources, a deadlock can occur as they wait indefinitely for the resources to become available.
Example:
public class upGradTutorials {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("Thread 1 acquired lock1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {
System.out.println("Thread 1 acquired lock2");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock2) {
System.out.println("Thread 2 acquired lock2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
System.out.println("Thread 2 acquired lock1");
}
}
});
thread1.start();
thread2.start();
}
}
The no preemption condition means that resources cannot be forcefully taken away from threads. This condition can contribute to deadlocks because if a thread is holding a resource that another thread requires, it must wait for the first thread to release the resource, even if the second thread could use it more efficiently.
Circular wait involves a circular chain of two or more threads, where each thread waits for a resource held by another thread. This circular dependency among the threads' resource requirements creates a deadlock, as no thread in the chain can proceed until it receives the resource held by another thread.
To avoid deadlocks in Java, you can follow several practices. Here are three techniques that can help prevent deadlocks:
If you acquire multiple locks, ensure they are always in the same order across all threads. This practice helps prevent different threads from acquiring locks in different orders, potentially leading to deadlocks.
public class upGradTutorials {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("Thread 1 acquired lock1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {
System.out.println("Thread 1 acquired lock2");
// Perform some operations using lock1 and lock2
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock2) {
System.out.println("Thread 2 acquired lock2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
System.out.println("Thread 2 acquired lock1");
// Perform some operations using lock1 and lock2
}
}
});
thread1.start();
thread2.start();
}
}
In this example, we have two threads (thread1 and thread2) that acquire locks lock1 and lock2, respectively. The critical point is that each thread acquires its respective lock before attempting to acquire the other lock. By ensuring a consistent order of lock acquisition across threads, we eliminate the possibility of nested locks and potential deadlocks.
Avoid acquiring locks when unnecessary, as unnecessary locking can increase the chances of deadlocks. If a resource doesn't require shared access among threads, consider using other synchronization mechanisms that do not rely on locks, such as atomic variables or concurrent collections.
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class upGradTutorials {
private static final Lock lock = new ReentrantLock();
private static int counter = 0;
public static void main(String[] args) {
Thread incrementThread = new Thread(() -> {
lock.lock();
try {
// Perform increment operation
counter++;
} finally {
lock.unlock();
}
});
Thread decrementThread = new Thread(() -> {
// No need to acquire the lock for decrement operation
// Perform decrement operation
counter--;
});
incrementThread.start();
decrementThread.start();
}
}
In this example, we have two threads (incrementThread and decrementThread) that perform increment and decrement operations on a shared counter variable. Notice that the decrementThread does not acquire the lock because it does not require synchronization with the incrementThread.
When working with multiple threads, utilize the join() method to ensure proper sequencing and synchronization. The join() method allows a thread to wait until another completes its execution. By using join() appropriately, you can avoid situations where a thread waits indefinitely for another thread to finish, reducing the chances of deadlocks.
public class upGradTutorials {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
System.out.println("Thread 1 started");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 1 completed");
});
Thread thread2 = new Thread(() -> {
System.out.println("Thread 2 started");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 2 completed");
});
thread1.start();
thread2.start();
try {
// Wait for thread1 to complete
thread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Main thread completed");
}
}
In this example, we have two threads (thread1 and thread2) that perform some operations and then complete their execution. The main thread starts both threads and then calls the join() method on thread1 to wait for its completion. This ensures that the main thread does not proceed further until thread1 has finished executing. Once thread1 completes, the main thread resumes execution and prints "Main thread completed" to the console.
When dealing with deadlocks in Java, keep the following points in mind:
Understanding and addressing a deadlock in Java is crucial for developing robust and efficient Java applications. You can mitigate the risk of deadlocks by following best practices such as avoiding circular wait, minimizing unnecessary locks, and using appropriate synchronization techniques.
To master the art of handling such scenarios in programming, you can sign up for a comprehensive course offered by upGrad.
1. How to handle a deadlock in Java?
Deadlocks can be handled by implementing prevention, detection, and resolution techniques (resource ordering, deadlock detection algorithms, etc.).
2. How can a deadlock be cleared?
Deadlocks can be cleared by releasing held resources, restarting affected threads, or redesigning the program's synchronization and resource usage to break the circular dependency causing the deadlock.
3. What is a synchronization deadlock in Java?
A synchronization deadlock in Java occurs when multiple threads are blocked and waiting for each other to release resources or locks, resulting in a situation where none of the threads can progress. It happens when the conditions of mutual exclusion, hold and wait, no preemption, and circular wait are satisfied.
PAVAN VADAPALLI
Popular
Talk to our experts. We’re available 24/7.
Indian Nationals
1800 210 2020
Foreign Nationals
+918045604032
upGrad does not grant credit; credits are granted, accepted or transferred at the sole discretion of the relevant educational institution offering the diploma or degree. We advise you to enquire further regarding the suitability of this program for your academic, professional requirements and job prospects before enrolling. upGrad does not make any representations regarding the recognition or equivalence of the credits or credentials awarded, unless otherwise expressly stated. Success depends on individual qualifications, experience, and efforts in seeking employment.
upGrad does not grant credit; credits are granted, accepted or transferred at the sole discretion of the relevant educational institution offering the diploma or degree. We advise you to enquire further regarding the suitability of this program for your academic, professional requirements and job prospects before enr...