Feb 15, 2012

Solving DeadLocks problems.

It has been a long time since I wrote my last post but as I promised this is the continuation. Last time I was talking about ReentrantLock class in the package java.util.concurrent.locks.

This package gives us an interface called 'Lock' which is implemented by the ReentrantLock class. This kind of object works very similar to synchronized code blocks, as we can have just one 'Lock' at the same time. Its main advantage is that it allows us to schedule alternative executions if the 'Lock' object has been obtained by another thread. The most important method of this object is the method tryLock, which will try to get the 'lock' of the object and if it is not possible it will return false.

This will give us the possibility to write code in order to take other actions. That's its main advantage with regard the synchronized code blocks, in which an infinite loop is started until it can get the 'lock'. Now we are going to use this kind of object in order to solve the deadlock problems in the previous example. We are going to have one Cashier class and one OperateCash class, we are going to create 2 objects from the Cashier class whose methods will be invoke in the OperateCashier class. This is the code example:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Deadlock {

 static class Cashier {

  private double balance;
  private final String name;
  public final Lock lock = new ReentrantLock();

  public Cashier(double balanceIni, String name) {
   this.balance = balanceIni;
   this.name = name;
  }

  public void debit(double value) {
   balance += value;
  }

  public void credit(double value) {
   balance -= value;
  }

  public double getBalance() {
   return balance;
  }

  public String getName() {
   return name;
  }

 }

 static class OperateCashier {

  public boolean transfer(Cashier cashierFrom, Cashier cashierTo,
    double value, String h) {

   Boolean lock1 = false;
   Boolean lock2 = false;

   System.out.println("Thread " + h + ": transfer cash from "
     + cashierFrom.getName() + " to " + cashierTo.getName());
   try {

    System.out.println("Thread " + h + ": get lock "
      + cashierFrom.getName());

    lock1 = cashierFrom.lock.tryLock();
    System.out.println("Thread " + h + ": get lock "
      + cashierTo.getName());

    lock2 = cashierTo.lock.tryLock();

   } finally {

    if (!(lock1 && lock2)) {

     if (lock1) {
      cashierFrom.lock.unlock();
     }

     if (lock2) {
      cashierTo.lock.unlock();
     }

    }

   }

   if (lock1 && lock2) {

    try {

     if (cashierFrom.getBalance() >= value) {

      cashierFrom.debit(value);
      cashierTo.credit(value);
      System.out.println("Thread " + h
        + ": transfer finished...");

     }

    } finally {

     cashierFrom.lock.unlock();
     cashierTo.lock.unlock();

    }

   } else {

    System.out.println("Thread " + h
      + ":It was not able to get the lock from both objects");

   }

   return (lock1 && lock2);

  }

 }

 public static void main(String[] args) {

  final Cashier cashier1 = new Cashier(60000, "CJ1");
  final Cashier cashier2 = new Cashier(80000, "CJ2");
  final OperateCashier opc = new OperateCashier();

  new Thread(new Runnable() {

   String nameThread = "H1";
   boolean go = false;
   long time = 100;

   public void run() {

    while (!go) {

     go = opc.transfer(cashier1, cashier2, 20000, nameThread);

     if (!go) {

      try {

       System.out.println("Thread " + nameThread
         + ": Wating " + time);

       Thread.sleep(time);

      } catch (InterruptedException e) {
      }

     }

    }

   }

  }).start();

  new Thread(new Runnable() {

   String nameThread = "H2";
   boolean go = false;
   long time = 100;

   public void run() {

    while (!go) {

     go = opc.transfer(cashier2, cashier1, 10000, nameThread);

     if (!go) {

      try {

       System.out.println("Thread " + nameThread
         + ": Wating " + time);

       Thread.sleep(time);

      } catch (InterruptedException e) {
      }

     }

    }

   }

  }).start();

 }

}

In the Cashier class in line number 10 a 'Lock' object has been created which is the object that will be used in order to lock the object thus preventing the other thread to use it. So when a Thread needs to use a Cashier object, it will invoke the method tryLock of the object Lock, this method will check if other thread have already locked the object, if so then it will return false, if not, it will lock the object and will return true.

The OperateCashier class have a method named 'transfer', this method makes money transfers from one cashier to another cashier, the two objects which represent those cashiers are given as parameters and so also the value to transfer and a String value that will tell us which Thread is running at the moment. In order for the transfer operation be sucessfull, it is needed that none other thread already be making the transfer. So it will be necessary lock both cashiers. Thus any other thread will have to wait until the current transaction is complete. 

In line number 50 we try to get the 'lock' of the cashier which is going to make the transfer, while in line number 54 we try to do the same with the cashier that will receive the transfer. The checking whether both cashier has been locked or not is done inside of the finally block, because in case it's not possible to lock both cashiers, it may be that at least one of them has been already locked so it should be released, we do not want an object locked for ever. In line number 58 again we check whether or not we managed lock both objects. If so then we go ahead and make the money transfer. Then we must unlock both objects. Finally a boolean value is returned pointing out if the transfer was sucessfull. 

Now let's see what is happening in the method main. The two Threads that will invoke the method transfer are created in there. Each one of these threads will try to make a money transfer. The first, from cashier1 to cashier2 and the second from cashier2 to cashier1. It's for sure that in some point during execution, one thread will find that other thread have already locked one or both cashiers, but it won't be a problem because in that case the method will return false and then we can decide what to do. In this case we will have a while loop trying to complete the transaction every 100 ms. eventually it will have its chance but in the real life you could decide to make other things.

if we execute this program many times. We can see that always both transaction are completed successfully, in whatever order, preventing a deadlock. So this is one of the ways to reduce deadlocks in programs.