Deadlock in Java

Deadlock in Java and Its Prevention with Examples

In this article, I am going to discuss Deadlock in Java and its Prevention with Examples. Please read our previous article where we discussed Inter Thread Communication in Java. At the end of this article, you will understand what is Deadlock in Java and how to solve the Deadlock problem in Java applications.

Deadlock in Java

Deadlock describes a situation where two or more threads are blocked forever, waiting for each other. When we execute multiple threads that are acting on the same object that is synchronized at the same time simultaneously then there is another problem that may occur called Deadlock. Deadlock may occur if one thread holding resource1 (obj 1) and waiting for resource2 (obj 1) release by thread2, at the same time thread2 is holding on resource2 (obj 2) and waiting for the resource1 (obj 1) to release by the thread1, in this case, two threads are continuously waiting and no thread will execute.

Deadlock in Java and Its Prevention with Examples

Example to Understand Deadlock in Java
public class Deadlock {
    public static void main(String[] args) {
        Deadlock test = new Deadlock();
        
        final A a = test.new A();
        final B b = test.new B();
 
        // Thread-1
        Runnable block1 = new Runnable() {
            public void run() {
                synchronized (a) {
                    try {
                        // Adding delay so that both threads can start trying to
                        // lock resources
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // Thread-1 have A but need B also
                    synchronized (b) {
                        System.out.println("In block 1");
                    }
                }
            }
        };
 
        // Thread-2
        Runnable block2 = new Runnable() {
            public void run() {
                synchronized (b) {
                    // Thread-2 have B but need A also
                    synchronized (a) {
                        System.out.println("In block 2");
                    }
                }
            }
        };
 
        new Thread(block1).start();
        new Thread(block2).start();
    }
 
  // Resource A
    private class A {
        private int i = 10;
 
        public int getI() {
            return i;
        }
 
        public void setI(int i) {
            this.i = i;
        }
    }
 
    // Resource B
    private class B {
        private int i = 20;
 
        public int getI() {
            return i;
        }
 
        public void setI(int i) {
            this.i = i;
        }
    }
}

Running the above code will result in a deadlock for very obvious reasons (explained above). Now we have to solve this issue.

Note: To resolve this deadlock situation there is no concept in Java, the programmer is only responsible for writing the proper logic to resolve the problem of deadlock.

How to resolve the Deadlock Problem in Java?

I believe, the solution to any problem lies in identifying the root of the problem. In our case, it is the pattern of accessing resources A and B, which is the main issue. So, to solve it, we will simply re-order the statements where the code is accessing shared resources.

public class Deadlock {
 
    public static void main(String[] args) {
        Deadlock test = new Deadlock();
        
 
        final A a = test.new A();
        final B b = test.new B();
 
        // Thread-1
        Runnable block1 = new Runnable() {
            public void run() {
                synchronized (b) {
                    try {
                        // Adding delay so that both threads can start trying to
                        // lock resources
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // Thread-1 have A but need B also
                    synchronized (a) {
                        System.out.println("In block 1");
                    }
                }
            }
        };
         
        // Thread-2
        Runnable block2 = new Runnable() {
            public void run() {
                synchronized (b) {
                    // Thread-2 have B but need A also
                    synchronized (a) {
                        System.out.println("In block 2");
                    }
                }
            }
        };
 
        new Thread(block1).start();
        new Thread(block2).start();
    }
 
  // Resource A
    private class A {
        private int i = 10;
 
        public int getI() {
            return i;
        }
 
        public void setI(int i) {
            this.i = i;
        }
    }
 
    // Resource B
    private class B {
        private int i = 20;
 
        public int getI() {
            return i;
        }
 
        public void setI(int i) {
            this.i = i;
        }
    }
}

Run again above class, and you will not see any deadlock kind of situation.

How to resolve the Deadlock Problem in Java

Methods of Handling Deadlock in Java

We can deal with the deadlock problem in one of three ways:

  1. We can use a protocol to prevent or avoid deadlocks, ensuring that the system will never enter a deadlock state.
  2. We can allow the system to enter a deadlock state, detect it, and recover.

We can ignore the problem overall, and pretend that deadlocks never occur in the system. This solution is used by most operating systems, including UNIX.

How do you detect deadlock in your code?

Deadlock Detection is only possible at run-time. Recovery from deadlock is either to abort or retry. There are many ways to detect deadlock. Few are listed below:

  1. Look at the code to see if a nested synchronized block is calling a synchronized method from another or if it is trying to get a lock on a different object. If that is the case, there is a good chance of deadlock, if the developer is not careful.
  2. When you actually get dead-locked while running the application. If this happens, try to take a thread dump, in Linux you can do this by the command “kill -3.” This will print the status of all threads in an application log file, and you can see which thread is locked on which object.
  3. Use jConsole/VisualVM. It will show you exactly which threads are getting locked and on which object.
How to Avoid Deadlock Condition in Java?

The idea of avoiding a deadlock does simply not allow the system to enter an unsafe state that may cause a deadlock. Deadlock avoidance requires some future knowledge of requests for resources from each of the participating processes.

  1. Avoid Nested Locks– Deadlocks can be avoided by always locking the locks in the same order. However, a nested lockout occurs exactly by two threads taking the locks in the same order. 
  2. Avoid Unnecessary Locks– Acquire locks only to those members which are required. Avoid giving locks to unnecessary threads by using lock-free data structures.
  3. Using Thread Join– A deadlock usually happens when one thread is waiting for the other to finish. In this case, we can use Thread.join with a maximum time that a thread will take.

Note: Never call a synchronized method of another class from a synchronized method. Follow a fixed order while acquiring and releasing locks.

In the next article, I am going to discuss Multithreading Exercises in Java with Examples. Here, in this article, I try to explain Deadlock in Java and its Prevention with Examples. I hope you enjoy this Deadlock in Java and its Prevention with Examples article.

Leave a Reply

Your email address will not be published. Required fields are marked *