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:
Similar to notify we have the signal method in the Condition class.
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:
- Two conditions objects were created to handle the two scenarios of overflow and underflow.
- 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."
- 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."
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 : 3In 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