Engineering Full Stack Apps with Java and JavaScript
Let us consider an example program that deadlocks, and then see how we could have avoided that. Below example is an example for a lock ordering deadlock.
The DeadLockExample class has two objects lockObject1 and lockObject2, which we will use as locks for synchronizing.
We will have two Runnables, which we will use for creating two threads: Runnable1 synchronizes on lockObject1 and try to get lockObject2, and Runnable2 synchronizes on lockObject2 and try to get lockObject1.
Sometimes, due to some lucky timing this might not go into deadlock, for example, if Runnable1 gets lockObject1 and also gets lockObject2 before Runnable2. Therefore for our demo, we will use a Thread.sleep() after acquiring first lock.
Remember that, Thread.sleep() call does not release the lock, but keeps the lock while sleeping, unlike the wait method that will release the lock and wait.
public class DeadLockExample {
final static Object lockObject1= new Object();
final static Object lockObject2= new Object();
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable1());
t1.setName("Thread A");
t1.start();
Thread t2 = new Thread(new Runnable2());
t2.setName("Thread B");
t2.start();
}
}
class Runnable1 implements Runnable
{
public void run()
{
synchronized (DeadLockExample.lockObject1)
{
System.out.println(Thread.currentThread().getName()+": Got lockObject1. Trying for lockObject2");
try {
Thread.sleep(500);
//DeadLockExample.lockObject1.wait(500);
//DeadLockExample.lockObject1.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (DeadLockExample.lockObject2)
{
System.out.println(Thread.currentThread().getName()+": Got lockObject2.");
}
}
}
}
class Runnable2 implements Runnable
{
public void run()
{
synchronized (DeadLockExample.lockObject2)
{
System.out.println(Thread.currentThread().getName()+": Got lockObject2. Trying for lockObject1");
try {
Thread.sleep(500);
//DeadLockExample.lockObject2.wait(500);
//DeadLockExample.lockObject2.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (DeadLockExample.lockObject1)
{
System.out.println(Thread.currentThread().getName()+": Got lockObject1.");
//DeadLockExample.lockObject1.notify();
}
}
}
}
The output will be:
Thread A: Got lockObject1. Trying for lockObject2
Thread B: Got lockObject2. Trying for lockObject1
Even though a program has the potential to deadlock, it may never deadlock at all, and will only happen with some bad timings. So we have used Thread.sleep() to simulate that bad timing, so that we can demonstrate deadlock.
Thread.sleep() will hold the lock while sleeping whereas, wait() method will release the lock and wait.
So replace Thread.sleep(500); with DeadLockExample.lockObject1.wait(500). The statement wait(500) will release the lock and wait for 500 milli seconds or until someone notifies it using a notify or notifyall method.
When you call wait, you are actually releasing your lock and hence other thread can acquire it and will then release it.
After 500 milliseconds, Thread 1 will again be runnable and acquire the released lock. So there won't be any deadlock.
Now, replace DeadLockExample.lockObject1.wait(500) with DeadLockExample.lockObject1.wait(). The program will hang again as noone is notifying the wait method to wake up. However this is not a deadlock. This is because the hold and wait condition of deadlock is not met here. You can confirm this by looking into the thread dump.
You can use jconsole, jvisualvm or jps to get the process id of your program and then run jstack <process-id> to get the thread stack dump.
You can use the same commands for a program running from an eclipse also.
Java Concurrency in Practice by Brian Goetz, David Holmes, DOug Lea, Tim Peierls, Joshua Bloch and Joseph Bowbeer.