Monday, 27 July 2015

Deadlock in Java multithreading

Deadlock describes a situation where two or more threads are blocked forever, waiting for each other.

To describe it in a simple manner let's assume there are two threads Thread-1 and Thread-2 and two objects obj1 and obj2. Thread-1 already holds a lock on obj1 and for further processing it needs a lock on obj2.At the same time Thread-2 holds a lock on obj2 and wants a lock on obj1. In that kind of scenario both threads will be waiting for each other forever to release lock they are already holding thus creating a deadlock.

deadlock in java

Deadlock in Java

Deadlock in a multi-threading environment may happen in case

  • One synchronized method is called from another synchronized method.
  • If there are nested synchronized blocks.
Let’s see example code for these scenarios –

Calling one synchronized method from other

public class DeadLockDemo {
 
 private final String name;
 
 public DeadLockDemo(String name){
  this.name = name;
 }
 public String getName() {
  return this.name;
 }
  
 public synchronized void call(DeadLockDemo caller){
  System.out.println(this.getName() + " has asked to call me " + caller.getName());
  try {
   // Introducing some delay
   Thread.sleep(100);
  } catch (InterruptedException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  //Calling another synchronized method
  caller.callMe(this);
 }
 
 
 public synchronized void callMe(DeadLockDemo caller){
  System.out.println(this.getName() + " has called me " + caller.getName());
 }
 
 public static void main(String[] args) {
     DeadLockDemo caller1 = new DeadLockDemo("caller-1");
     DeadLockDemo caller2 = new DeadLockDemo("caller-2");
     // Thread 1   
     new Thread(new Runnable() {
         public void run() { caller1.call(caller2); }
     }).start();

     //Thread 2  
     new Thread(new Runnable() {
         public void run() { caller2.call(caller1); }
     }).start();

 }

}

Output

caller-1 has asked to call me caller-2
caller-2 has asked to call me caller-1

This code may result in a deadlock for the two created threads, if you are executing this program you'll have to stop the program forcefully.

Here we are creating 2 objects of class DeadLockDemo caller1 and caller2. Then call() method is called with those objects in to separate threads. As you already know that if you are using synchronized instance methods then the thread will have exclusive lock one per instance.

So both threads will enter the call() method one using the lock of caller1 object and another using the lock of caller2 object. Within call() method there is another call to a synchronized method callMe(), notice that the thread which has lock on object caller1 will have to get a lock on object caller2 to call this method. Whereas the thread which has lock on object caller2 will have to get a lock on object caller1 to call this method.

But the lock on object caller1 and caller2 are already held by respective threads causing the threads to wait for each other to release those locks. This will lead to a deadlock and callMe() method will never be called.

nested synchronized block

class Test{
    private final String name;
    public Test(String name){
        this.name = name;
    }
    public String getName() {
        return this.name;
    }
}

class ThreadA implements Runnable{
    private Test test1;
    private Test test2;
    ThreadA(Test test1, Test test2){
        this.test1 = test1;
        this.test2 = test2;
    }
    @Override
    public void run() {
        synchronized(test1){
            System.out.println("" + test1.getName());
            synchronized(test2){
                System.out.println("Reached here");
            }
        }
        
    }
    
}

class ThreadB implements Runnable{

    private Test test1;
    private Test test2;
    ThreadB(Test test1, Test test2){
        this.test1 = test1;
        this.test2 = test2;
    }
    @Override
    public void run() {
        synchronized(test2){
            System.out.println("" + test2.getName());
            synchronized(test1){
                System.out.println("Reached here");
            }
        }   
    }
}
public class DeadLockDemo1{

    public static void main(String[] args) {
        Test test1 = new Test("Test-1");
        Test test2 = new Test("Test-2");
        Thread t1 = new Thread(new ThreadA(test1, test2));
        Thread t2 = new Thread(new ThreadB(test1, test2));
        t1.start();
        
        t2.start();
    }
}

Output

Test-1
Test-2

As can be seen from the output that both the threads are locked while trying to acquire lock of another object. So "Reached here" never get printed. Thread t1 will start execution of run method in ThreadA and acquire lock on object test1 and then try to acquire lock on object test2. Meanwhile Thread t2 will start execution of run method in ThreadB and acquire lock on object test2 and then try to acquire lock on object test1. So both threads are trying to acquire a lock which is already held by another thread. Thus causing a deadlock.

How to avoid deadlocks

  • Avoiding inconsistent lock ordering - Code given in example 2 shows how, order in which locks are acquired can result in deadlock. Fortunately this kind of deadlock is easy to avoid. If we change the order in such a way that each method acquires both of the locks in the same order, two or more threads executing these methods may not cause deadlock.
    class ThreadA implements Runnable{
        private Test test1;
        private Test test2;
        ThreadA(Test test1, Test test2){
            this.test1 = test1;
            this.test2 = test2;
        }
        @Override
        public void run() {
            synchronized(test1){
                System.out.println("" + test1.getName());
                synchronized(test2){
                    System.out.println("Reached here");
                }
            }
            
        }
        
    }
    
    class ThreadB implements Runnable{
    
        private Test test1;
        private Test test2;
        ThreadB(Test test1, Test test2){
            this.test1 = test1;
            this.test2 = test2;
        }
        @Override
        public void run() {
            synchronized(test1){
                System.out.println("" + test2.getName());
                synchronized(test2){
                    System.out.println("Reached here");
                }
            }   
        }
    }
    

    If you notice in both the threads, order in which locks are acquired, is same now.

  • Try to use synchronized blocks - It is always advisable to use synchronized blocks rather than synchronizing the whole method. Using synchronized block will result in the scope of synchronization reduced which means lock will be held for less time.In example 1 if we use synchronized block rather than synchronizing the whole call() method we may avoid the deadlock.

That's all for this topic Deadlock in Java multi-threading. If you have any doubt or any suggestions to make please drop a comment. Thanks!


Related Topics

  1. Thread states in Java multithreading
  2. What if run() method called directly instead of start() method
  3. Volatile in Java
  4. Thread priorities in Java multithreading
  5. Race condition in Java multi-threading
  6. Java Multi-threading interview questions

You may also like -

No comments:

Post a Comment