Friday, July 29, 2016

What is a deadlock and how to avoid it in Java?


What Is Deadlock In Java?
Deadlock in java is a condition which occurs when two or more threads get blocked waiting for each other for an infinite period of time to release the resources (Locks) they hold. Deadlock is the common problem in multi-threaded programming which can completely stops the execution of an application. So, extra care needs to be taken while writing the multi-threaded programs so that deadlock never occurs.

Let’s look at one simple example of deadlock condition.

Class Shared
{
    synchronized void methodOne(Shared s)
    {
        Thread t = Thread.currentThread();
        System.out.println(t.getName()+"is executing methodOne...");
        System.out.println(t.getName()+"is calling methodTwo...");
        s.methodTwo(this);
        System.out.println(t.getName()+"is finished executing methodOne...");
    }

    synchronized void methodTwo(Shared s)
    {
        Thread t = Thread.currentThread();
        System.out.println(t.getName()+"is executing methodTwo...");
        System.out.println(t.getName()+"is calling methodOne...");
        s.methodOne(this);
        System.out.println(t.getName()+"is finished executing methodTwo...");
    }
}

public class DeadLockInJava
{
    public static void main(String[] args)
    {
        final Shared s1 = new Shared();
        final Shared s2 = new Shared();
        Thread t1 = new Thread()
        {
            public void run()
            {
                s1.methodOne(s2);
            }
        };

        Thread t2 = new Thread()
        {
            @Override
            public void run()
            {
                s2.methodTwo(s1);
            }
        };

        t1.start();

        t2.start();
    }
}

In the above multi-threaded program, thread t1 and t2 are concurrent threads i.e. they are executing their task simultaneously. There are two Shared class objects, s1 and s2, which are shared by both the threads. Shared class has two synchronized methods, methodOne() and methodTwo(). That means, only one thread can execute these methods at a given time.

First, thread t1 enters the methodOne() of s1 object by acquiring the object lock of s1. At the same time, thread t2 also enters the methodTwo() of s2 object by acquiring the object lock of s2methodOne() of s1object, currently executing by thread t1, calls methodTwo() of s2 object from it’s body. So, thread t1 tries to acquire the object lock of s2 object. But object lock of s2 object is already acquired by thread t2. So, thread t1waits for thread t2 to release the object lock of s2 object.
At the same time, thread t2 is also executing methodTwo() of s2 object. methodTwo() of s2 object also makes a call to methodOne() of s1 object. So, thread t2 tries to acquire the object lock of s1 object. But, it is already acquired by thread t1. So, thread t2 also waits for thread t1 to release the object lock of s1 object.
Thus, both the threads wait for each other to release the object locks they own. They wait for infinite period of time to get the object locks owned by opposite threads. This condition of threads waiting forever is called Deadlock.

How to Avoid the Deadlock in Java
Deadlock is a dangerous condition, if it happens, it will bring the whole application to complete halt. So, extra care needs to be taken to avoid the deadlock. Followings are some tips that can be used to avoid the deadlock in java.

  • Try to avoid nested synchronized blocks.
A nested synchronized block makes a thread to acquire another lock while it is already holding one lock. This may create the deadlock if another thread wants the same lock which is currently held by this thread.

synchronized (Lock A)
{
    //Some statements

    synchronized (Lock B)
    {
        //Try to avoid this block
    }
}

  • Lock Ordering :
If you needed nested synchronized blocks at any cost, then make sure that threads acquire the needed locks in some predefined order. For example, if there are three threads t1, t2 and t3 running concurrently and they needed locks A, B and C in the following manner,

Thread t1 :
        Lock A
        Lock B
Thread t2 :
        Lock A
        Lock C
Thread t3 :
        Lock A
        Lock B
        Lock C

In the above scenario, t1 needs A and B locks, t2 needs A and C locks and t3 needs A, B and C locks. If you define an order to acquire the locks like, Lock A must be acquired before Lock B and Lock B must be acquired before Lock c, then deadlock never occurs in the above case.
If you define such lock ordering, then thread t2 never acquire lock C and t3 never acquire lock B and lock C until they got lock A. They will wait for lock A until it is released by t1. After lock A is released by t1, any one of these threads will acquire lock A on the priority basis and finishes their task. Other thread which is waiting for lock A, will never try to acquire remaining locks.
By defining such lock ordering, you can avoid the deadlock.

  • Lock Timeout :
Another deadlock preventive tip is to specify the time for a thread to acquire the lock. If it fails to acquire the specified lock in the given time, then it should give up trying for a lock and retry after some time. Such method of specifying time to acquire the lock is called lock timeout.

  • Lock the code where it is actually needed. For example, If you want only some part of the method to be thread safety, then lock only that part not the whole method.

void method()
{
    //Some statements
    synchronized (this)
    {
        //Locking only some part of the method
    }
    //Some statements
}

No comments:

Post a Comment