Deadlock in Java



Deadlock Java Multithreading

What is the deadlock situation in Java?

In a multithreading environment, there are many chances for a Java deadlock situation to occur. In this tutorial, we will see how to detect and avoid a deadlock situation in Java.

A deadlock occurs when two threads wait for each other infinitely and is blocked forever. The threads are blocked since it requires the same lock. For example, consider two threads T1 and T2. T1 acquires lock1 and T2 acquires lock2. Now during execution, T1 wants to acquire lock2 and T2 wants to acquire lock1. Since both the threads are waiting for each other to release the lock, it results in a java deadlock condition.

Both the threads acquire the locks by using the synchronized keyword.

The below diagram will help you understand how the deadlock condition occurs.

Deadlock in Java

Example of Java deadlock

Below is an example of a deadlock in java. Thread t1 initially locks value1 and goes to sleep, while Thread t2 starts and locks value2 and goes to sleep. After the sleep of t1, it tries to acquire the lock of value2 but since it is locked by t2 it waits. Similarly, after the sleep of t2, it tries to acquire the lock of value1 but since it is locked by t1 it waits. Now both t1 and t2 are infinitely waiting for each other’s lock and hence results in a deadlock condition. To stop the execution of the below code, we need to forcefully terminate the program.

Hence, while developing any software, we need to ensure that we do not end up in any java deadlock condition.

public class DeadlockDemo {

  public static void main(String[] args) {
    final String value1 = "Lock1";
    final String value2 = "Lock2";
    
    Thread t1 = new Thread() {
      public void run() {
        synchronized(value1) {
          System.out.println("Thread 1 locked value1");
          try {
            Thread.sleep(1000);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
          System.out.println("Thread 1 waiting for value2 lock");
          synchronized(value2) {
            System.out.println("Thread 1 locked value2");
          }
        }
      }
    };
    
    Thread t2 = new Thread() {
      public void run() {
        synchronized(value2) {
          System.out.println("Thread 2 locked value2");
          try {
            Thread.sleep(1000);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
          System.out.println("Thread 2 waiting for value1 lock");
          synchronized(value1) {
            System.out.println("Thread 2 locked value1");
          }
        }
      }
    };
    
    t1.start();
    t2.start();
  }
}
Thread 1 locked value1
Thread 2 locked value2
Thread 2 waiting for value1 lock
Thread 1 waiting for value2 lock

How to detect deadlock

We can detect if a deadlock condition has occurred in java by using the command prompt. The command depends upon the OS type and Java version. Using the command, we can retrieve the Thread dump to analyze the root cause of the deadlock situation.

Below is the command if using Windows and Java8.

jcmd $PID Thread.print

To get the Process ID PID, we can use the below command

jps

Below are the process id and the thread dump output using the above command. It clearly states that 1 deadlock is present in the above code.

C:\Users\admin>jps
21872 DeadlockDemo
20468 DeadlockDemo
23140 DeadlockDemo
29412 Jps
9832

C:\Users\admin>jcmd 23140 Thread.print
23140:
2021-03-11 15:53:24
Full

thread

 dump Java HotSpot(TM) 64-Bit Server VM (13+33 mixed mode, sharing):

Threads class SMR info:
_java_thread_list=0x000002034ace5120, length=12, elements={
0x000002034a278000, 0x000002034a27b000, 0x000002034a298800, 0x000002034a297000,
0x000002034a2a0800, 0x000002034a2a3800, 0x000002034a2a4800, 0x000002034a298000,
0x000002034a296000, 0x000002034a29a800, 0x000002034a299800, 0x000002034a29b000
}

"Reference Handler" #2 daemon prio=10 os_prio=2 cpu=0.00ms elapsed=26468.36s tid=0x000002034a278000 nid=0x158 waiting on condition  [0x000000dad3fff000]
   java.lang.Thread.State: RUNNABLE
        at java.lang.ref.Reference.waitForReferencePendingList([email protected]/Native Method)
        at java.lang.ref.Reference.processPendingReferences([email protected]/Reference.java:241)
        at java.lang.ref.Reference$ReferenceHandler.run([email protected]/Reference.java:213)

"Finalizer" #3 daemon prio=8 os_prio=1 cpu=0.00ms elapsed=26468.36s tid=0x000002034a27b000 nid=0x60a4 in Object.wait()  [0x000000dad40ff000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait([email protected]/Native Method)
        - waiting on <0x000000008950aed8> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove([email protected]/ReferenceQueue.java:155)
        - locked <0x000000008950aed8> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove([email protected]/ReferenceQueue.java:176)
        at java.lang.ref.Finalizer$FinalizerThread.run([email protected]/Finalizer.java:170)

"Signal Dispatcher" #4 daemon prio=9 os_prio=2 cpu=0.00ms elapsed=26468.36s tid=0x000002034a298800 nid=0x2180 runnable  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Attach Listener" #5 daemon prio=5 os_prio=2 cpu=0.00ms elapsed=26468.36s tid=0x000002034a297000 nid=0xa58 waiting on condition  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #6 daemon prio=9 os_prio=2 cpu=15.63ms elapsed=26468.36s tid=0x000002034a2a0800 nid=0x5e6c waiting on condition  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
   No compile task

"C1 CompilerThread0" #8 daemon prio=9 os_prio=2 cpu=15.63ms elapsed=26468.36s tid=0x000002034a2a3800 nid=0x7140 waiting on condition  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
   No compile task

"Sweeper thread" #9 daemon prio=9 os_prio=2 cpu=0.00ms elapsed=26468.36s tid=0x000002034a2a4800 nid=0x3d64 runnable  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Service Thread" #10 daemon prio=9 os_prio=0 cpu=0.00ms elapsed=26468.34s tid=0x000002034a298000 nid=0x234c runnable  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Common-Cleaner" #11 daemon prio=8 os_prio=1 cpu=0.00ms elapsed=26468.34s tid=0x000002034a296000 nid=0x30ec in Object.wait()  [0x000000dad48fe000]
   java.lang.Thread.State: TIMED_WAITING (on object monitor)
        at java.lang.Object.wait([email protected]/Native Method)
        - waiting on <0x00000000895e28a8> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove([email protected]/ReferenceQueue.java:155)
        - locked <0x00000000895e28a8> (a java.lang.ref.ReferenceQueue$Lock)
        at jdk.internal.ref.CleanerImpl.run([email protected]/CleanerImpl.java:148)
        at java.lang.Thread.run([email protected]/Thread.java:830)
        at jdk.internal.misc.InnocuousThread.run([email protected]/InnocuousThread.java:134)

"Thread-0" #12 prio=5 os_prio=0 cpu=0.00ms elapsed=26468.33s tid=0x000002034a29a800 nid=0x5090 waiting for monitor entry  [0x000000dad49fe000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at DeadlockDemo$1.run(DeadlockDemo.java:18)
        - waiting to lock <0x00000000895eac68> (a java.lang.String)
        - locked <0x00000000895eac38> (a java.lang.String)

"Thread-1" #13 prio=5 os_prio=0 cpu=15.63ms elapsed=26468.33s tid=0x000002034a299800 nid=0x3cf8 waiting for monitor entry  [0x000000dad4aff000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at DeadlockDemo$2.run(DeadlockDemo.java:35)
        - waiting to lock <0x00000000895eac38> (a java.lang.String)
        - locked <0x00000000895eac68> (a java.lang.String)

"DestroyJavaVM" #14 prio=5 os_prio=0 cpu=62.50ms elapsed=26468.33s tid=0x000002034a29b000 nid=0x6254 waiting on condition  [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"VM Thread" os_prio=2 cpu=140.63ms elapsed=26468.38s tid=0x000002034a272800 nid=0x47d4 runnable

"GC Thread#0" os_prio=2 cpu=0.00ms elapsed=26468.39s tid=0x000002032b49a800 nid=0x62e4 runnable

"G1 Main Marker" os_prio=2 cpu=0.00ms elapsed=26468.39s tid=0x000002032b4ac000 nid=0x73a4 runnable

"G1 Conc#0" os_prio=2 cpu=0.00ms elapsed=26468.39s tid=0x000002032b4ae000 nid=0x1f04 runnable

"G1 Refine#0" os_prio=2 cpu=0.00ms elapsed=26468.38s tid=0x000002034a100800 nid=0x64cc runnable

"G1 Young RemSet Sampling" os_prio=2 cpu=0.00ms elapsed=26468.38s tid=0x000002034a101800 nid=0x68f8 runnable
"VM Periodic Task Thread" os_prio=2 cpu=78.13ms elapsed=26468.34s tid=0x000002034ad17000 nid=0x4f30 waiting on condition

JNI global refs: 4, weak refs: 0


Found one Java-level deadlock:
=============================
"Thread-0":
  waiting to lock monitor 0x000002034a282000 (object 0x00000000895eac68, a java.lang.String),
  which is held by "Thread-1"
"Thread-1":
  waiting to lock monitor 0x000002034a284100 (object 0x00000000895eac38, a java.lang.String),
  which is held by "Thread-0"

Java stack information for the threads listed above:
===================================================
"Thread-0":
        at DeadlockDemo$1.run(DeadlockDemo.java:18)
        - waiting to lock <0x00000000895eac68> (a java.lang.String)
        - locked <0x00000000895eac38> (a java.lang.String)
"Thread-1":
        at DeadlockDemo$2.run(DeadlockDemo.java:35)
        - waiting to lock <0x00000000895eac38> (a java.lang.String)
        - locked <0x00000000895eac68> (a java.lang.String)

Found 1 deadlock.

Deadlock prevention

There are several methods to prevent deadlock in java to a certain extent though not always.

  • Avoid nested locks: One main reason for a deadlock is providing multiple locks to multiple threads.
  • Avoid unnecessary locks: Always try to avoid providing unnecessary locks to threads. We need to lock only the required resources for a particular thread.
  • Thread join: There are chances of deadlock when one thread is waiting for another thread to complete the execution. If we do not use the join method appropriately, threads may wait forever resulting in a deadlock.
  • Lock timeout: We can also prevent a deadlock by using the timeout condition if a thread does not acquire the lock within a specific time.
  • Proper order of acquiring locks: If we acquire the locks in proper order, then it is possible to avoid a deadlock situation.

For example, if we reorder the locks in proper order for the deadlock code, we can prevent this situation. Since t2 first tries to acquire the lock of value1, it will lock only when t1 releases. By then t1 releases value1 lock and acquires lock on value2 and so on. Hence there is interthread resource lock dependency and therefore deadlock never occurs.

public class DeadlockDemo {

  public static void main(String[] args) {
    final String value1 = "Lock1";
    final String value2 = "Lock2";
    
    Thread t1 = new Thread() {
      public void run() {
        synchronized(value1) {
          System.out.println("Thread 1 locked value1");
          try {
            Thread.sleep(1000);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
          System.out.println("Thread 1 waiting for value2 lock");
          synchronized(value2) {
            System.out.println("Thread 1 locked value2");
          }
        }
      }
    };
    
    Thread t2 = new Thread() {
      public void run() {
        synchronized(value1) {
          System.out.println("Thread 2 locked value1");
          try {
            Thread.sleep(1000);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
          System.out.println("Thread 2 waiting for value2 lock");
          synchronized(value2) {
            System.out.println("Thread 2 locked value2");
          }
        }
      }
    };
    
    t1.start();
    t2.start();

  }

}
Thread 1 locked value1
Thread 1 waiting for value2 lock
Thread 1 locked value2
Thread 2 locked value1
Thread 2 waiting for value2 lock
Thread 2 locked value2

Reference