As we already learned, in the case of multithread development, we have to use synchronization. The synchronization is built on locking. You have to be careful when using synchronization for multithreaded applications because your threads can become deadlocked.
The easiest and the most popular example of the deadlock from life is the road with one way and bidirectional traffic.
Deadlock is a blocking of a set of threads that are competing for a set of resources. A thread can make progress, but it does not mean that there is not a deadlock somewhere else.
The most common deadlock is a self deadlock or recursive deadlock. It is a situation when a thread tries to acquire a lock it is already holding. It is very easy to program self deadlock or recursive deadlock by mistake.
The simplest example of application that causes deadlock:
public class DeadlockExample { public static void main(String[] args){ deadlock(); } public synchronized static void deadlock() { try { Thread t = new Thread(DeadlockExample::deadlock); t.start(); t.join(); } catch (Exception ex) { ex.printStackTrace(); } } }
Our thread started itself in another thread and waits for this thread to finish. We did this in the synchronized method. So, it will wait for itself.
The detection of deadlocks in Java is not easy. Even logs may not be very helpful. If you observe that your code with threads is not performing as well as it was then it may be due to a deadlock.
How to detect deadlock in Java?
There are many answers to this question. First of all, it’s good to look at code for a nested synchronized block, a call of one synchronized method from another or tries to get a lock on a different object. There is a good chance of deadlock there if a developer is not very careful.
You can also actually run your application, get dead-locked and take a thread dump.
In Linux OS you can do this by the command “kill -3”. This command will print the status of all threads in an application log, There you can find out which thread is locked on which object. The thread dump can be analyzed with a tool like fastthread.io.
If you are running your application on Windows OS in cmd, you can collect thread dump with command jcmd $PID Thread.print.
One more tool to find deadlock is the JConsole/VisualVM. It will show you threads that are getting locked and on which object.
We will look at some ways to do this.
Example of deadlock
We are going to create an example with two threads. Let’s assume that we have two shared resources SHARED_RESOURCES_A and SHARED_RESOURCES_B. The first thread has a synchronization block on SHARED_RESOURCES_A. Inside that block, there is the nested synchronization block on SHARED_RESOURCES_B. The second thread also has nested synchronization blocks, but the outer block is on SHARED_RESOURCES_B, the inner block is on SHARED_RESOURCES_A. We are starting two threads. The first thread is blocking shared resources A, the second – shared resources B. We are waiting a bit, but the application is stuck. After we are printing the statuses of our threads. Here is the code:
package concurrent; public class DeadlockExample { final static Object SHARED_RESOURCES_A = new Object(); final static Object SHARED_RESOURCES_B = new Object(); public static void main(String[] args) throws InterruptedException { MyThread1 myThread1 = new MyThread1(); MyThread2 myThread2 = new MyThread2(); myThread1.start(); myThread2.start(); Thread.sleep(3000); System.out.println("State of the thread 1 is " + myThread1.getState()); System.out.println("State of the thread 2 is " + myThread2.getState()); } } class MyThread1 extends Thread { public void run() { synchronized (DeadlockExample.SHARED_RESOURCES_A) { System.out.println("Thread 1 locked shared resources A"); synchronized (DeadlockExample.SHARED_RESOURCES_B) { System.out.println("Thread 1 locked shared resources B"); } } } } class MyThread2 extends Thread { public void run() { synchronized (DeadlockExample.SHARED_RESOURCES_B) { System.out.println("Thread 2 locked shared resources B"); synchronized (DeadlockExample.SHARED_RESOURCES_A) { System.out.println("Thread 2 locked shared resources A"); } } } }
The output of this application will be:
Thread 2 locked shared resources B Thread 1 locked shared resources A State of the thread 1 is BLOCKED State of the thread 2 is BLOCKED
The first thread cannot acquire the lock for shared resources B (SHARED_RESOURCES_B), because the second thread holds it. The second thread cannot acquire the lock for shared resources A (SHARED_RESOURCES_A), the first thread holds it. Here we are getting the deadlock.
States of our threads are BLOCKED. In case of normal finishing of an application thread stated have to be TERMINATED.
Let’s try to detect this deadlock. If you are using Eclipse IDE you can go to the debug view and set it to show monitor information via the view menu. Then run the current example with the deadlock in debug mode. After, click the “pause” button. This will cause the JVM and suspend the execution of all threads.
It will be easy for you to diagnose and report the problem with the Eclipse framework. Our threads that are in contention will be marked in red. An object that a thread is waiting for and an object that thread owns are indicated there. Look at screenshot shown below:
As you can see, our first thread (MyThread1) owns an object with id = 24 and waiting for an object with id = 23. And vise versa, the second thread (MyThread2) owns an object with id = 23 and waiting for an object with id = 24. Therefore, our two threads are in the deadlock.
Let’s fix our deadlock. All we need is just to order our locks in both threads:
class MyThread1 extends Thread { public void run() { synchronized (DeadlockExample.SHARED_RESOURCES_B) { System.out.println("Thread 1 locked shared resources B"); synchronized (DeadlockExample.SHARED_RESOURCES_A) { System.out.println("Thread 1 locked shared resources A"); } } } } class MyThread2 extends Thread { public void run() { synchronized (DeadlockExample.SHARED_RESOURCES_B) { System.out.println("Thread 2 locked shared resources B"); synchronized (DeadlockExample.SHARED_RESOURCES_A) { System.out.println("Thread 2 locked shared resources A"); } } } }