Avoid Deadlock in Java

What is Deadlock ?

Deadlock is a programming situation in a multi-threaded environment where each process or thread is waiting to release resources that are controlled by another process. Deadlock conditions occur with at least two threads and two or more resources.

This usually occurs when multiple threads need the same locks but acquire them in different orders. Java multi-threaded programming is blocked due to synchronized keyword.

How To Avoid Deadlock ?

Although it is not possible to completely avoid deadlock situations but we can follow some ways or tips to avoid them.

  • Avoid Nested Locks : Avoid granting locks to multiple threads. This is the main reason for the deadlock conditions. This usually happens when we allocates lock to multiple threads.
  • Avoid unnecessary locks: locks should be assigned to critical threads only. Assign locks on unnecessary threads that cause a deadlock condition.
  • Using Thread Join : Deadlocks often occur when one thread waits for the other to finish. In this case, we can use Thread.join with the maximum time that a thread will take.

Example – Program to Detect Deadlock

 
public class DeadLockDetect {
 
    public static void main(String[] args) {
        DeadLockDetect deadLockDetect = new DeadLockDetect();
 
         final Employee1 emp1 = deadLockDetect.new Employee1();
         final Employee2 emp2 = deadLockDetect.new Employee2();
 
        // Thread-1
        Runnable thread1 = new Runnable() {
            public void run() {
                synchronized (emp1) {
                    try {
                       
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // Thread-1 have emp1 but need emp2 also
                    synchronized (emp2) {
                        System.out.println("In Thread 1");
                    }
                }
            }
        };
 
        // Thread-2
        Runnable thread2 = new Runnable() {
            public void run() {
                synchronized (emp2) {
                    // Thread-2 have emp2 but need emp1 also
                    synchronized (emp1) {
                        System.out.println("In Thread 2");
                    }
                }
            }
        };
 
        new Thread(thread1).start();
        new Thread(thread2).start();
    }
 
    // Resource Employee 1
    private class Employee1 {
        private int empId = 10;

        public int getEmpId() {
          return empId;
        }

        public void setEmpId(int empId) {
          this.empId = empId;
        }
 
        
    }
 
    // Resource Employee 2
    private class Employee2 {
        private int empId = 20;

        public int getEmpId() {
          return empId;
        }

        public void setEmpId(int empId) {
          this.empId = empId;
        }
 
        
    }
}

In above case, Thread-1 has Employee 1 but need Employee 2 to complete processing and similarly Thread-2 has resource Employee 2 but need Employee 1 first. Running the above code will cause a deadlock for very obvious reasons.

Example – Avoid deadlock in above case

The main problem is the access pattern to resources Employee 1 and Employee 2. So to solve it, just reposition the statement where the code is accessing shared resources.

 
public class DeadLockDetect {
 
    public static void main(String[] args) {
        DeadLockDetect deadLockDetect = new DeadLockDetect();
 
         final Employee1 emp1 = deadLockDetect.new Employee1();
         final Employee2 emp2 = deadLockDetect.new Employee2();
 
      // Thread-1
         Runnable thread1 = new Runnable() {
             public void run() {
                 synchronized (emp2) {
                     try {
                         
                         Thread.sleep(1000);
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                     synchronized (emp1) {
                         System.out.println("In Thread 1");
                     }
                 }
             }
         };
          
         // Thread-2
         Runnable thread2 = new Runnable() {
             public void run() {
                 synchronized (emp2) {
                   
                     synchronized (emp1) {
                         System.out.println("In Thread 2");
                     }
                 }
             }
         };
         
        new Thread(thread1).start();
        new Thread(thread2).start();
    }
 
    // Resource Employee 1
    private class Employee1 {
        private int empId = 10;

        public int getEmpId() {
          return empId;
        }

        public void setEmpId(int empId) {
          this.empId = empId;
        }
 
        
    }
 
    // Resource Employee 2
    private class Employee2 {
        private int empId = 20;

        public int getEmpId() {
          return empId;
        }

        public void setEmpId(int empId) {
          this.empId = empId;
        }
 
        
    }
}

Output

In Thread 1
In Thread 2