How to avoid Deadlock in Java with Example

In Java, deadlock is a situation where no thread can continue execution and the entire program appears to freeze. This occurs when two or more threads keep waiting forever for each other to release the resources they need to proceed.

Deadlock usually occurs when two or more threads simultaneously compete to access the same resources that are not released in the correct order. For example, if Thread 1 is holding Lock A and waiting for Lock B, while Thread 2 is holding Lock B and waiting for Lock A. If neither thread releases their lock, they will be stuck in a deadlock forever.

Here's an example code that demonstrates a deadlock scenario in Java:

public class DeadLockExample {
   private static final Object lock1 = new Object();
   private static final Object lock2 = new Object();

   public static void main(String[] args) {

      Thread thread1 = new Thread(() -> {
         synchronized (lock1) {
	     System.out.println("Thread 1 acquired lock 1");
	     try {
	          Thread.sleep(100);
	     } catch (InterruptedException e) {
	          e.printStackTrace();
	     }
	     synchronized (lock2) {
	          System.out.println("Thread 1 acquired lock 2");
	     }
	  }
      });

      Thread thread2 = new Thread(() -> {
         synchronized (lock2) {
            System.out.println("Thread 2 acquired lock 2");
	    try {
	         Thread.sleep(100);
	    } catch (InterruptedException e) {
	         e.printStackTrace();
	    }
	    synchronized (lock1) {
	         System.out.println("Thread 2 acquired lock 1");
	    }
         }
      });

      thread1.start();
      thread2.start();
   }

}

In this code, two threads (thread1 and thread2) are created. Each thread attempts to acquire two locks (lock1 and lock2), but in reverse order. If you run this code, it may result in a deadlock. Thread 1 acquires lock1 and then waits for lock2 to be released. At the same time, Thread 2 acquires lock2 and then waits for lock1 to be released. Since both threads are waiting for each other to release the locks they hold, the program becomes deadlocked and cannot proceed further.


How to Detect Deadlock in Java?

Developers can use the following tools to identify the threads that are in a deadlock state. These tools allow you to take a thread dump, which provides information about the state of all threads in the JVM, including the thread that may be causing a deadlock.

  • jstack
  • jconsole
  • VisualVM

Another way to detect deadlock in Java is to use the ThreadMXBean class, which provides methods to detect and monitor deadlocks programmatically. The findDeadlockedThreads() method of the ThreadMXBean class can be used to obtain an array of thread IDs that are deadlocked. Additionally, the isDeadlocked() method can be used to check if a specific thread is in a deadlock state.

It is important to note that detecting a deadlock does not necessarily solve the problem. When a deadlock is detected, steps must be taken to resolve it, such as releasing locks in a consistent order or using timeout mechanisms to prevent threads from waiting indefinitely.


How to Avoid Deadlock in Java?

Deadlocks in Java can be prevented by following some best practices as follows:

  • Avoid nested locks because nested locks can increase the complexity of the code and make it difficult to maintain. It is best to avoid nested locks whenever possible.
  • Acquire locks in a consistent order. If multiple locks need to be acquired by a thread, they should be acquired in a consistent order across all threads. This can prevent circular dependencies and ensure that deadlocks do not occur.
  • Release locks when they are no longer needed because holding locks for a long time can increase the chances of deadlocks occurring. It is important to release locks as soon as they are no longer needed.
  • Use timeout mechanisms when acquiring locks, it is a good practice to use timeout mechanisms that release the lock if it is not acquired within a certain time frame. This can prevent threads from waiting indefinitely and reduce the chances of deadlocks occurring.

Here's an example code that demonstrates the use of a consistent locking order to avoid deadlock in Java:

public class Example {

    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();

    public static void main(String[] args) {

        Thread thread1 = new Thread(() -> {
            synchronized (lock1) {
                System.out.println("Thread 1 acquired lock 1");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock2) {
                    System.out.println("Thread 1 acquired lock 2");
                    // Run thread1's task
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (lock1) {
                System.out.println("Thread 2 acquired lock 1");
                synchronized (lock2) {
                    System.out.println("Thread 2 acquired lock 2");
                    // Run thread2's task
                }
            }
        });

        thread1.start();
        thread2.start();
    }

}

In this example, both threads acquire lock1 before attempting to acquire lock2. This consistent order of acquiring locks prevents any possibility of a circular dependency and potential deadlock.

By following best practices, you can help avoid deadlocks in your Java code.