April 10, 2024

Deadlock in Java With Examples

In a multi-threaded environment there may come a situation when one thread is waiting for a resource that is locked by another thread, which in turn is waiting for another thread and so on until this dependency loops back to the first waiting thread. Thus all the threads are waiting for each other to release the resources to make any further progress and blocked forever in the process. This scenario is called deadlock in multi-threading.

Deadlock example

To explain deadlock in Java we can take a simple scenario of two threads Thread1 and Thread2, where Thread1 holds a lock on obj1 and waiting to acquire a lock on obj2. At the same time Thread2 has a lock on obj2 and waiting to acquire lock on obj1. Here both threads are blocked in a circular loop where Thread1 is waiting to acquire lock on obj2 and Thread2 is waiting to acquire lock on obj1 thus creating a deadlock.

Deadlock in Java

Deadlock in Java scenarios

You may get deadlock in Java because of the improper use of the synchronized keyword. Scenarios when deadlock may happen are as follows.

  1. Nested synchronized blocks with reverse ordering of objects.
  2. Calling one synchronized method from another where methods are not using the same object to synchronize.

Deadlock in Java example

First example shows the scenario when there are nested synchronized blocks with reverse ordering of objects.

class ThreadA implements Runnable{
  private Object obj1;
  private Object obj2;
  ThreadA(Object obj1, Object obj2){
    this.obj1 = obj1;
    this.obj2 = obj2;
  }
  @Override
  public void run() {
    synchronized(obj1){
      System.out.println(Thread.currentThread().getName() + " acquired " + "obj1 lock");
      synchronized(obj2){
        System.out.println(Thread.currentThread().getName() + " acquired " + "obj2 lock");
      }
    }       
  }  
}

class ThreadB implements Runnable{
  private Object obj1;
  private Object obj2;
  ThreadB(Object obj1, Object obj2){
    this.obj1 = obj1;
    this.obj2 = obj2;
  }
  @Override
  public void run() {
    synchronized(obj2){
      System.out.println(Thread.currentThread().getName() + " acquired " + "obj2 lock");
      synchronized(obj1){
        System.out.println(Thread.currentThread().getName() + " acquired " + "obj1 lock");
      }
    }   
  }
}

public class DLDemo {
  public static void main(String[] args) {
    Object obj1 = new Object();
    Object obj2 = new Object();
    Thread t1 = new Thread(new ThreadA(obj1, obj2));
    Thread t2 = new Thread(new ThreadB(obj1, obj2));
    t1.start();
    t2.start();
  }
}
Output
Thread-0 acquired obj1 lock
Thread-1 acquired obj2 lock

In ThreadA’s run method synchronization is first done on obj1 and later on obj2. In ThreadB’s run method synchronization is reversed, it is first done on obj2 and later on obj1. That may result in a deadlock where t1 gets a lock on obj1 and waiting to acquire a lock on obj2. At the same time obj2 has acquired a lock on obj2 and waiting to acquired a lock on obj1.

Another deadlock Java example shows the scenario where one synchronized method is called from another.

public class DLDemo {
  public synchronized void method1(DLDemo obj){
    System.out.println(Thread.currentThread().getName() + " In Method1");
    try {
      Thread.sleep(100);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    //Calling another synchronized method
    obj.method2(this);
  }
		 	 
  public synchronized void method2(DLDemo obj2){
    System.out.println("In Method2");
  }

  public static void main(String[] args) {
    DLDemo obj1 = new DLDemo();
    DLDemo obj2 = new DLDemo();
		
    new Thread(new Runnable() {
      public void run() { obj1.method1(obj2); }
    }).start();

    //Thread 2  
    new Thread(new Runnable() {
      public void run() { obj2.method1(obj1); }
    }).start();
  }
}
Output
Thread-0 In Method1
Thread-1 In Method1

In the code there are two instances of DLDemo class, one thread calls the synchronized method method1 using obj1 and another thread calls it using obj2. Which means Thread1 holds a lock on obj1 and Thread2 holds a lock on obj2.

With in the synchronized method method1 there is a call to another synchronized method method2, both of the threads are trying to call method2 with the object whose lock is held by another object thus leading to a deadlock.

How to avoid deadlock in Java

With multi-threading programming deadlock may occur and there is no language support as such to prevent deadlocks. You will have to write your code used by multiple threads carefully to avoid deadlocks. Here we’ll go through the scenarios shown above and see how deadlocks can be avoided in those scenarios.

1. As shown in the previous scenarios one of the reason for the deadlock in Java is the way locks are acquired, if you have nested synchronization then you can acquire the object locks in the same order rather than in reverse order for both threads.

Changed nested synchronization code

class ThreadA implements Runnable{
  private Object obj1;
  private Object obj2;
  ThreadA(Object obj1, Object obj2){
    this.obj1 = obj1;
    this.obj2 = obj2;
  }
  @Override
  public void run() {
    synchronized(obj1){
      System.out.println(Thread.currentThread().getName() + " acquired " + "obj1 lock");
        synchronized(obj2){
          System.out.println(Thread.currentThread().getName() + " acquired " + "obj2 lock");
        }
    }       
  }  
}

class ThreadB implements Runnable{
  private Object obj1;
  private Object obj2;
  ThreadB(Object obj1, Object obj2){
    this.obj1 = obj1;
    this.obj2 = obj2;
  }
  @Override
  public void run() {
    synchronized(obj1){
      System.out.println(Thread.currentThread().getName() + " acquired " + "obj1 lock");
      synchronized(obj2){
        System.out.println(Thread.currentThread().getName() + " acquired " + "obj2 lock");
      }
    }   
  }
}
Output
Thread-0 acquired obj1 lock
Thread-0 acquired obj2 lock
Thread-1 acquired obj1 lock
Thread-1 acquired obj2 lock

As you can see from the output now the deadlock is avoided.

2. Using synchronized block to minimize the synchronization to the critical section code only will also help in avoiding the deadlock in Java.

In the second scenario rather than synchronizing the whole method synchronized block can be used.

public class DLDemo {
  public void method1(DLDemo obj){
    System.out.println(Thread.currentThread().getName() + " In Method1");
    try {
      Thread.sleep(100);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    synchronized(this){
      //Calling another synchronized method
      obj.method2(this);
    }
  }
		  
  public void method2(DLDemo obj2){
    System.out.println("In Method2");
    synchronized(this){
      System.out.println("In Method2 synchronized block");
    } 
  }

  public static void main(String[] args) {
    DLDemo obj1 = new DLDemo();
    DLDemo obj2 = new DLDemo();
    
    new Thread(new Runnable() {
      public void run() { obj1.method1(obj2); }
    }).start();

    //Thread 2  
    new Thread(new Runnable() {
      public void run() { obj2.method1(obj1); }
    }).start();
  }
}

3- By using static synchronization in Java. If two object instances are used two threads using two separate objects can still enter a synchronized method or block with their separate object locks. Static synchronization helps in this case because the lock will be acquired at the class level then.

How to debug a deadlock in Java

Detecting a deadlock in Java is not easy, even logs may be of little help. If you observe that your multi-threaded code is not performing as well as it was then it may be due to a deadlock and best thing is to get a thread dump of the application and analyze it.

You can use jstack utility to get a thread dump by providing the pid of the Java application. That pid can be obtained by running jps command. For example, if I run the program where deadlock was created due to nested synchronization, then I can get the thread dump using following steps.

1- By using jps command I can get the pid of the Java application.

Jps

5968
7408 DLDemo
13960 Jps

2- Run the jstack command with the pid as argument.

Jstack 7408

3- Get the thread dump and analyze it. Here some of the relevant portion of the thread dump is displayed.

"Thread-1" #11 prio=5 os_prio=0 tid=0x000000001b3e1000 nid=0x145c waiting for monitor entry [0x000000001bade000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.knpcode.ThreadB.run(DLDemo.java:33)
        - waiting to lock <0x00000000d5bfaff0> (a java.lang.Object)
        - locked <0x00000000d5bfb000> (a java.lang.Object)
        at java.lang.Thread.run(Unknown Source)

"Thread-0" #10 prio=5 os_prio=0 tid=0x000000001b3e0000 nid=0x379c waiting for monitor entry [0x000000001b9de000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.knpcode.ThreadA.run(DLDemo.java:15)
        - waiting to lock <0x00000000d5bfb000> (a java.lang.Object)
        - locked <0x00000000d5bfaff0> (a java.lang.Object)
        at java.lang.Thread.run(Unknown Source)



"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x0000000004d4c800 nid=0x2b34 in Object.wait() [0x000000001acee000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000d5b88ec0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(Unknown Source)
        - locked <0x00000000d5b88ec0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(Unknown Source)
        at java.lang.ref.Finalizer$FinalizerThread.run(Unknown Source)

"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x0000000004d42000 nid=0x6cc in Object.wait() [0x000000001abef000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000d5b86b68> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Unknown Source)
        at java.lang.ref.Reference.tryHandlePending(Unknown Source)
        - locked <0x00000000d5b86b68> (a java.lang.ref.Reference$Lock)
        at java.lang.ref.Reference$ReferenceHandler.run(Unknown Source)


Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x0000000004d47868 (object 0x00000000d5bfaff0, a java.lang.Object),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x0000000004d4a0f8 (object 0x00000000d5bfb000, a java.lang.Object),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
        at com.knpcode.ThreadB.run(DLDemo.java:33)
        - waiting to lock <0x00000000d5bfaff0> (a java.lang.Object)
        - locked <0x00000000d5bfb000> (a java.lang.Object)
        at java.lang.Thread.run(Unknown Source)
"Thread-0":
        at com.knpcode.ThreadA.run(DLDemo.java:15)
        - waiting to lock <0x00000000d5bfb000> (a java.lang.Object)
        - locked <0x00000000d5bfaff0> (a java.lang.Object)
        at java.lang.Thread.run(Unknown Source)

Found 1 deadlock.

That's all for the topic Deadlock in Java With Examples. If something is missing or you have something to share about the topic please write a comment.


You may also like

No comments:

Post a Comment