How can we avoid a deadlock in Java?



This article explains several strategies to avoid deadlock in Java, including a brief introduction to deadlock, strategies, and respective examples.

Deadlock in Java

In case of multi-threading, a deadlock is a programming situation where two or more threads are holding resources needed by other threads and are blocked forever, waiting for each other (to release the required resource).

A deadlock condition will occur with at least two threads and two or more resources.

How To Avoid Deadlock in Java?

Following is a list of strategies to avoid the deadlock in Java:

  • Avoid Nested Locks: A deadlock mainly happens when we give locks to multiple threads. Avoid giving a lock to multiple threads if we have already given it to one.

  • Avoid Unnecessary Locks: We can have a lock only for those members which are required. Having a lock unnecessarily can lead to a deadlock.

  • Using Thread.join(): A deadlock condition appears when one thread is waiting other to finish. If this condition occurs, we can use Thread.join() with the maximum time the execution will take.

Avoid Deadlock by Avoiding the Nested Locks

Sometimes, the deadlocks also occur due to nested locks, where a thread holds one lock and waits for another. By avoiding nested locks and acquiring one lock at a time, we can avoid the deadlock.

Example

In the following program, we avoid deadlock by not acquiring nested locks. Instead, each thread completes its work with one lock, releases it, and then tries to acquire the next lock:

public class DeadlockTest {
   public static void main(String[] args) throws InterruptedException {
      Object obj1 = new Object();
      Object obj2 = new Object();
      Object obj3 = new Object();
      Thread t1 = new Thread(new SyncThread(obj1, obj2), "t1");
      Thread t2 = new Thread(new SyncThread(obj2, obj3), "t2");
      t1.start();
      Thread.sleep(2000);
      t2.start();
      Thread.sleep(2000);
   }
}
class SyncThread implements Runnable {
   private Object obj1;
   private Object obj2;
   public SyncThread(Object o1, Object o2){
      this.obj1=o1;
      this.obj2=o2;
   }
   @Override
   public void run() {
      String name = Thread.currentThread().getName();
      System.out.println(name + " acquiring lock on " + obj1);
      synchronized (obj1) {
         System.out.println(name + " acquired lock on " + obj1);
         work();
      }
      System.out.println(name + " released lock on " + obj1);
      System.out.println(name + " acquiring lock on " + obj2);
      synchronized (obj2) {
         System.out.println(name + " acquired lock on " + obj2);
         work();
      }
      System.out.println(name + " released lock on " + obj2);
      System.out.println(name + " finished execution.");
   }
   private void work() {
      try {
         Thread.sleep(5000);
      } catch (InterruptedException ie) {
         ie.printStackTrace();
      }
   }
}

The above program produces the following output:

t1 acquiring lock on java.lang.Object@27a54c50
t1 acquired lock on java.lang.Object@27a54c50
t2 acquiring lock on java.lang.Object@2ee6a5aa
t2 acquired lock on java.lang.Object@2ee6a5aa
t1 released lock on java.lang.Object@27a54c50
t1 acquiring lock on java.lang.Object@2ee6a5aa
t2 released lock on java.lang.Object@2ee6a5aa
t2 acquiring lock on java.lang.Object@20214e06
t1 acquired lock on java.lang.Object@2ee6a5aa
t2 acquired lock on java.lang.Object@20214e06
t1 released lock on java.lang.Object@2ee6a5aa
t2 released lock on java.lang.Object@20214e06
t1 finished execution.
t2 finished execution.

Using Thread.join() Method

In Java, another way to avoid deadlock is by using the Thread.join() method. This method blocks the current thread until the thread on which it is called finishes execution.

Deadlock can occur when threads wait for each other. The join() method helps avoid such deadlock scenarios.

Example

The example given below uses the Thread.join() method to avoid the deadlock:

public class AvoidDeadlockWithJoin {
   public static void main(String[] args) {
   
      Thread t1 = new Thread(() -> {
         System.out.println("Thread 1 Started....");
         try {
            Thread.sleep(2000);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
         System.out.println("Thread 1 Finished....");
      });
      
      Thread t2 = new Thread(() -> {
         System.out.println("Thread 2 Started....");
         try {
            Thread.sleep(2000);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
         System.out.println("Thread 2 Finished....");
      });
      
      // Start Thread 1
      t1.start();
      try {
         //using join() method
         t1.join(); // Wait for t1 to finish before starting t2
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      // Start Thread 2 after t1 is finished
      t2.start();
      try {
         // Wait for t2 to finish
         t2.join();
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      
      System.out.println("Main thread, all threads finished");
   }
}

Following is the output of the above program:

Thread 1 Started....
Thread 1 Finished....
Thread 2 Started....
Thread 2 Finished....
Main thread , all threads finished
Updated on: 2025-05-14T14:07:06+05:30

5K+ Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements