Witscale Test Center

12.3 Access to shared data/code with synchronization > 12.3.5 Deadlock


12.3.5 Deadlock

The most dreaded situation in a Java program with multiple threads is the deadlock. This situation typically occurs when two (or more) threads are blocked, with each waiting for an object lock which the other thread is holding. This can happen in various situations. The simplest one to imagine is with two threads. Listing 12.6 shows a class Reminder with two methods goHome() and goToOffice(). Each of these methods has a synchronized code and need locks on two objects myCar and myHome for successful completion. Acquiring just one lock is not sufficient.


The Reminder class has a potential of causing a deadlock when multiple threads will try to execute its run method. Let us see how this can happen by actually starting two threads on the same instance of Reminder. In the following code, the class ThreadTester is creating two threads on reminder object and starting it.

 

public class ThreadTester {

  public static void main(String[] args) {

    Reminder reminder = new Reminder();

    Thread thread1 = new Thread(reminder);

    Thread thread2 = new Thread(reminder);

    thread1.start();

    thread2.start();

  }

}

 

One possible scenario is thread1 starts executing first. It will start executing the run()method. Since initially the variable wantToGoHome will be true, thread1 will start executing the goHome() method. With the synchronized (myCar), it will acquire the lock on myCar object and then it will go to sleep for 1000 milliseconds. It is likely that thread2 will get a chance to run. Now wantToGoHome is false and hence it will start executing the goToOffice method. With the synchronized (myHome), it will acquire the lock on myHome object and then it too will go to sleep for 1000 milliseconds. Meantime thread1 will probably wake up and will encounter the second synchronized block in goHome().It will see the synchronized (myHome) and  it try to acquire the lock on myHome object. But now the myHome object’s lock is already held with thread2 which is sleeping. So thread1 will be blocked on lock for myHome. Eventually thread2 wakes up and it sees the second synchronized block in goToOffice() method it was executing -  synchronized (myCar) {}. Now it will try to acquire the lock on myCar object. However, thread1 is already holding the lock. So thread2 will also be blocked on lock for myHome.

Thus thread1 will hold myCar’s lock and block to get myHome’s lock. At the same time, thread2 will hold myHome’s lock and block to get myCar’s lock. Both will never give up the lock they are holding. In that case you will probably get the output as:

 

Thread-1 has myCar's lock and wants myHome's lock

Thread-2 has myHome's lock and wants myCar's lock

 


After printing this, the thread’s will run forever (of course as long as the JVM is running) without any resolution. Neither can ever run until it gives up the lock, but they both will never give the lock they already acquired until the synchronized code is executed. Thus, both of them will be blocked and waiting for release of lock that is never going to happen. Figure 12.12 illustrates how thread1 and thread2 can be deadlocked forever each seeking the lock held by another.

 

 

Figure 12.12 Deadlocked threads Thread-1 and Thread-2 waiting forever to get the locks held by each other

Since thread scheduler guarantees nothing, you cannot say for sure that ThreadTester will always cause the deadlock. It is possible that thread1 will complete the execution before thread2 even get to execute. In that case, deadlock will not happen. But you can predict the possibility of deadlock by looking at the code and the class Reminder in listing 12. has one. For exam, you need to understand how deadlock can cause threads to be blocked forever. Blocking is one of the conditions that prevent thread execution (objective 7.2).

 

Even when there is a little possibility of deadlock in your code, bear in mind that - deadlock is something you never want to happen. There are number of ways to avoid deadlocks. One simple way is to acquire the  locks in a predetermined fashion so that a thread will never block for a lock which it will get only when it releases one of the locks it is holding. For instance, in our example if both goHome() and goToOffice() methods should acquire the locks in same sequence. Either both should acquire the myCar’s lock first followed by the myHome’s lock. Or they both should acquire myHome’s lock first and then myCar’s lock. In that case, the chance of deadlock is minimal. The detailed study of strategies to avoid deadlocks is beyond the scope of this book. However, you may need to learn them whenever you do the deeper study of Java threading.