Question:
I came across the term deadlock in Java. But, I can't understand it. So, I would like to ask these questions:
- What is a deadlock?
- Why and how to avoid a deadlock?
Answer:
Deadlock is the cyclic dependency that happens between two or more threads sharing two or more resources (variable, code block, etc.). In other words, a T1
thread depends on another T2
, and this T2
depends, in turn, on T1
.
This type of dependency happens exclusively due to a flaw in the logic implemented by the programmer, who, when developing a multithreaded application, needs to be concerned not only with deadlock , but with other concepts related to concurrent programming, such as starvation , livelock and race condition , the latter being the latter probably the most common to happen.
A code example in which deadlock occurs:
public class TestThread {
public static Object Lock1 = new Object();
public static Object Lock2 = new Object();
public static void main(String args[]) {
ThreadDemo1 T1 = new ThreadDemo1();
ThreadDemo2 T2 = new ThreadDemo2();
T1.start();
T2.start();
}
private static class ThreadDemo1 extends Thread {
public void run() {
synchronized (Lock1) {
System.out.println("Thread 1: Holding lock 1...");
try { Thread.sleep(10); }
catch (InterruptedException e) {}
System.out.println("Thread 1: Waiting for lock 2...");
synchronized (Lock2) {
System.out.println("Thread 1: Holding lock 1 & 2...");
}
}
}
}
private static class ThreadDemo2 extends Thread {
public void run() {
synchronized (Lock2) {
System.out.println("Thread 2: Holding lock 2...");
try { Thread.sleep(10); }
catch (InterruptedException e) {}
System.out.println("Thread 2: Waiting for lock 1...");
synchronized (Lock1) {
System.out.println("Thread 2: Holding lock 1 & 2...");
}
}
}
}
}
When the application is executed and both threads started, T1
acquires the lock (as if it were an access exclusivity), represented by the word synchronized
, on the Lock1
object. That is, until T1
releases the lock on this object (that is, until the code block inside synchronized
is completely executed), no other thread can access it. You can see that within this first synchronized
block, there is another lock attempt (represented by the other synchronized
block), this time over Lock2
. This means, in small terms, that the original lock on the Lock1
object will only be released if the T1
thread also gets the lock on Lock2
and executes the code inside this second block. In summary, at this point in the code T1
has lock on Lock1
and NEEDS lock on Lock2
to terminate its routine and release all resources to other threads .
In turn, T2
, when it starts, acquires the lock on Lock2
and, to finish its execution and release the resources, it NEEDS the lock on Lock1
.
With this overview, it is easy to see what is happening: thread T1
locked object Lock1
and needs to gain the lock on Lock2
to finish, but T2
already locked Lock2
and to finish it needs to gain the lock on Lock1
, which in turn is locked by T1
. Here's the cyclic dependency, or deadlock . The output of this code when running:
Thread 1: Holding lock 1...
Thread 2: Holding lock 2...
Thread 1: Waiting for lock 2...
Thread 2: Waiting for lock 1...
At this point, your application is in a permanent standby state and will stay that way forever and that's why you never want a deadlock
.
And here comes the question: how to fix the code so that it doesn't happen? Just reverse the order of the objects you want to lock . Ie both threads will try first lockar the object Lock1
(and managing to also lockar Lock2
), run the code and finish, then allowing the next thread to acquire the lock and also run your code because:
public class TestThread {
public static Object Lock1 = new Object();
public static Object Lock2 = new Object();
public static void main(String args[]) {
ThreadDemo1 T1 = new ThreadDemo1();
ThreadDemo2 T2 = new ThreadDemo2();
T1.start();
T2.start();
}
private static class ThreadDemo1 extends Thread {
public void run() {
synchronized (Lock1) {
System.out.println("Thread 1: Holding lock 1...");
try {
Thread.sleep(10);
} catch (InterruptedException e) {}
System.out.println("Thread 1: Waiting for lock 2...");
synchronized (Lock2) {
System.out.println("Thread 1: Holding lock 1 & 2...");
}
}
}
}
private static class ThreadDemo2 extends Thread {
public void run() {
synchronized (Lock1) {
System.out.println("Thread 2: Holding lock 1...");
try {
Thread.sleep(10);
} catch (InterruptedException e) {}
System.out.println("Thread 2: Waiting for lock 2...");
synchronized (Lock2) {
System.out.println("Thread 2: Holding lock 1 & 2...");
}
}
}
}
}
Running, here is the output :
Thread 1: Holding lock 1...
Thread 1: Waiting for lock 2...
Thread 1: Holding lock 1 & 2...
Thread 2: Holding lock 1...
Thread 2: Waiting for lock 2...
Thread 2: Holding lock 1 & 2...
And your application will be ready to continue running whatever comes next.