Engineering Full Stack Apps with Java and JavaScript
Java concurrency package give you the ability to use explicit locks. With explicit locks, you can see if a lock is available and acquire only if it is available. This way you can avoid deadlocks. We already wrote a deadlock simulation program without using explicit locks and then a deadlock simulation program using explicit locks. Next, we will write it to use the tryLock feature to avoid deadlocks.
We will use the Lock interface and its implementation ReentrantLock for our example. Both these belong to the package java.util.concurrent.locks. Reentrant lock behaves similar to the intrinsic locking mechanism using synchronized keyword when we use its lock and unlock methods. Calling lock on a ReentrantLock implementation can be compared to entering a synchronized region and calling unlock can be compared to exiting a synchronized region. Since calling the unlock is an explicit and important task, it is always a good practice to have it inside a finally block. We will see explicit locks in detail later, but for now refer to the java doc for any API related doubts.
Lock interface also has a method called tryLock which will acquire the lock only if it is free at the time of invocation. We can make use of the tryLock feature to avoid deadlock. We will need to try acquiring the lock within a loop and release locks if we can’t acquire both locks. We are avoiding the necessary condition of hold and wait to prevent deadlock here. We will also need to introduce some random timing between tries to avoid an infinite loop.
package deadlock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class DeadLockExplicitLockTryLock {
final static Lock lock1 = new ReentrantLock();
final static Lock lock2 = new ReentrantLock();
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable5());
t1.setName("Thread A");
t1.start();
Thread t2 = new Thread(new Runnable6());
t2.setName("Thread B");
t2.start();
}
}
class Runnable5 implements Runnable
{
public void run() {
boolean done = false;
while (!done) {
if (DeadLockExplicitLockTryLock.lock1.tryLock()) {
try {
System.out.println(Thread.currentThread().getName()
+ ": Got lockObject1. Trying for lockObject2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (DeadLockExplicitLockTryLock.lock2.tryLock()) {
try {
System.out.println(Thread.currentThread().getName()
+ ": Got lockObject2.");
done = true;
} finally {
DeadLockExplicitLockTryLock.lock2.unlock();
}
}
} finally {
DeadLockExplicitLockTryLock.lock1.unlock();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
class Runnable6 implements Runnable
{
public void run()
{
boolean done = false;
while (!done) {
if (DeadLockExplicitLockTryLock.lock2.tryLock()) {
try {
System.out.println(Thread.currentThread().getName()
+ ": Got lockObject1. Trying for lockObject2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (DeadLockExplicitLockTryLock.lock1.tryLock()) {
try {
System.out.println(Thread.currentThread().getName()
+ ": Got lockObject2.");
done = true;
} finally {
DeadLockExplicitLockTryLock.lock1.unlock();
}
}
} finally {
DeadLockExplicitLockTryLock.lock2.unlock();
try {
Thread.sleep(750);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
I got the below output while executing
Thread A: Got lockObject1. Trying for lockObject2
Thread B: Got lockObject2. Trying for lockObject1
Thread B: Got lockObject1.
Thread A: Got lockObject1. Trying for lockObject2
Thread A: Got lockObject2.
The actual output may slightly vary form one run to another.