Search This Blog

Thursday 24 July 2014

Conditions and Locks

In the previous post we saw how the Lock API could be used as an alternative to synchronized block. We also saw how the Lock API allowed us to do chain locking. There is also a condition object closely associated with Locks:
Condition factors out the Object monitor methods (wait, notify and notifyAll) into 
distinct objects to give the effect of having multiple wait-sets per object, by 
combining them with the use of arbitrary Lock implementations. Where a Lock replaces 
the use of synchronized methods and statements, a Condition replaces the use of 
the Object monitor methods.
Conditions (also known as condition queues or condition variables) provide a means 
for one thread to suspend execution (to "wait") until notified by another thread 
that some state condition may now be true. Because access to this shared state 
information occurs in different threads, it must be protected, so a lock of some 
form is associated with the condition. The key property that waiting for a 
condition provides is that it atomically releases the associated lock and suspends 
the current thread, just like Object.wait.
As it is all so closely related with wait notify, I decided to create a simple class that used synchronized methods and wait notify to solve a simplified version of the producer consumer problem.
class Oven {
  int ovenSize = 2;
  Queue<String> trays = new LinkedList<String>();

  void addToOven(String cake) throws InterruptedException {
    synchronized (trays) {
      if (trays.size() == 2) {
        System.out.println("Unable to add " + cake + " to oven...");
        trays.wait();
      }

      System.out.println("Adding " + cake + " to oven");
      trays.add(cake);
      trays.notifyAll();
    }

  }

  String getCake() throws InterruptedException {
    String cake = null;
    synchronized (trays) {
      if (trays.size() == 0) {
        System.out.println(" No cake in oven.. ");
        trays.wait();
      }

      cake = trays.remove();
      System.out.println(cake + " has been removed from oven.. ");
      trays.notifyAll();
    }
    return cake;
  }

}
In the Oven class above we used guarded blocks to prevent the oven overflowing or 'under flowing' . But when using locks as we do not synchronize on the resource, so how to use wait/ notify? This is where Condition objects come into the picture. The condition object is closely coupled with our Lock instance.
Lock lock = new ReentrantLock();
Condition condition  = lock.newCondition();
I decided to implement the above class with Locks and Conditions:
class Oven {
  int ovenSize = 2;
  Queue<String> trays = new LinkedList<String>();

  final Lock ovenLock = new ReentrantLock();
  final Condition ovenFull = ovenLock.newCondition();
  final Condition ovenEmpty = ovenLock.newCondition();

  void addToOven(String cake) throws InterruptedException {
    ovenLock.lock();
    if (trays.size() == 2) {
      System.out.println("Unable to add " + cake + " to oven...");
      ovenFull.await();
    }

    System.out.println("Adding " + cake + " to oven");
    trays.add(cake);
    ovenEmpty.signalAll();
    ovenLock.unlock();
  }

  String getCake() throws InterruptedException {
    String cake = null;

    ovenLock.lock();
    if (trays.size() == 0) {
      System.out.println(" No cake in oven.. ");
      ovenEmpty.await();
    }
    cake = trays.remove();
    System.out.println(cake + " has been removed from oven.. ");
    ovenFull.signalAll();
    ovenLock.unlock();
    return cake;
  }

}
As seen above:
  1. Two conditions objects were created to handle the two scenarios of overflow and underflow.  
  2. A wait like behavior was obtained with the await method. "The lock associated with this Condition is atomically released and the current thread becomes disabled for thread scheduling purposes and lies dormant."
  3. When the notification needs to be performed we use the signalAll method. This behavior is similar to the wait method. "If any threads are waiting on this condition then they are all woken up. Each thread must re-acquire the lock before it can return from await."
To run the code, I created a producer and consumer.
class Baker implements Runnable {
  private int maxCapacity;
  Oven oven;

  Baker(int maxCapacity, Oven oven) {
    this.maxCapacity = maxCapacity;
    this.oven = oven;
  }

  public void run() {
    String threadName = Thread.currentThread().getName();
    int piecesMade = 1;
    while (piecesMade <= maxCapacity) {
      String cake = "Cake No : " + piecesMade;
      System.out.println(threadName + " is adding " + cake);

      try {
        oven.addToOven(cake);
        piecesMade++;
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }

}
class Glutton implements Runnable {
  private int maxCapacity;
  Oven oven;

  Glutton(int maxCapacity, Oven oven) {
    this.maxCapacity = maxCapacity;
    this.oven = oven;
  }

  public void run() {
    String threadName = Thread.currentThread().getName();
    int piecesEaten = 1;
    while (piecesEaten <= maxCapacity) {
      try {
        String cake = oven.getCake();
        System.out.println(threadName + " is eating " + cake);
        piecesEaten++;
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }

}
The output :
No cake in oven..
Cake Master is adding Cake No : 1
Adding Cake No : 1 to oven
Cake No : 1 has been removed from oven..
Big Fred is eating Cake No : 1
No cake in oven..
Cake Master is adding Cake No : 2
Adding Cake No : 2 to oven
Cake No : 2 has been removed from oven..
Big Fred is eating Cake No : 2
No cake in oven..
Cake Master is adding Cake No : 3
Adding Cake No : 3 to oven
Cake No : 3 has been removed from oven..
Big Fred is eating Cake No : 3
In case of await, the thread waits until it is signaled or interrupted. If we do not want the interrupt to wake the thread than we have the awaitUninterruptibly method. There are also overloaded version of await that accept time until which they wait or a date until which they wait.
Similar to notify we have the signal method in the Condition class.

No comments:

Post a Comment