Sunday, March 2, 2014

wait and notify in Java

The lock/monitor mechanism provided in Java prevents more than one thread from accessing a shared resource. Java achieves it by allowing only one thread to get hold of methods/block using synchronization technique. Thread safety comes at the cost of no direct communication between threads.

Some problem require inter-thread communication, though; like producer-consumer problems. Producers and consumers co-operate with each other and decide next action, which is difficult to achieve using default monitor lock. There is a need to bring in coordination between multiple threads. Java provides wait/notify methods to handle such problem which require two or more threads/tasks to talk to each other.

wait and notify methods

These methods are part of the superclass, Object. Let's cover them briefly before jumping to the example:

wait/wait(..): This method provides a way to synchronize activities between tasks. Within a task when you call wait(), the execution of the task gets suspended and the lock on the object is released. Once the lock is released, it can get acquired by another task.
Overloaded wait method (i.e. wait(100) takes an argument in milliseconds (similar to sleep). 

notify/notifyAll: Obvious question after call to wait is, what makes the task to resume? 
Task resumes if the same object calls notify or notifyAll method. If more than one tasks are in wait state, then notifyAll needs to be called instead of notify. So notify wakes up task(s) which is/are in wait state.

Example to understand wait and notify

Let's take a trivial example to show co-operation between two threads using wait and notify methods. ThreadCoordination class has two methods, one generates the random number and other one prints the same (getNextRandom() method waits until the next number is generated through the method, generateNextRandom()). The main method, test these two methods by creating two threads. These threads are generating and printing first ten random numbers.

 import java.util.Random;  
 import java.util.concurrent.TimeUnit;  
 public class ThreadCoordination {  
      private volatile int randomNum = 0;  
      Random rand = new Random(101);  
      public synchronized void generateNextRandom() throws InterruptedException{  
           while(randomNum != 0){  
                wait();  
           }  
           TimeUnit.SECONDS.sleep(2); //delay of 2 seconds  
           randomNum = rand.nextInt(101);  
           notify();  
      }  
      public synchronized void getNextRandom() throws InterruptedException {  
           while(randomNum == 0){  
                wait();  
           }  
           System.out.println(" Next Number is : "+ randomNum);  
           randomNum = 0 ; //reset the value  
           notify();  
      }  
      //Test method  
      public static void main(String[] args) {  
           final ThreadCoordination tc = new ThreadCoordination();  
           new Thread(){  
                public void run(){  
                     for(int i=0; i<10; i++){  
                          try {  
                               tc.getNextRandom();  
                          } catch (InterruptedException e) {  
                               e.printStackTrace();  
                          }  
                     }  
                }  
           }.start();  
           new Thread(){  
                public void run(){  
                     for(int i=0; i<10; i++){  
                          try {  
                               tc.generateNextRandom();  
                          } catch (InterruptedException e) {  
                               e.printStackTrace();  
                          }  
                     }  
                }  
           }.start();  
      }  
 }  

Output:

 Next Number is :21
 Next Number is :27
 Next Number is :82
 Next Number is :60
 Next Number is :69
 Next Number is :13
 Next Number is :11
 Next Number is :43
 Next Number is :17
 Next Number is :56

Note, both methods are using wait as well as notify to complement each other.  getNextRandom() waits until next random number gets generated in method generateNextRandom(). And generateNextRandom() waits until the generated number gets printed (and variable gets resets).
Also, wait() needs to be called from the loop; calling it from an if condition will not help. 

BlockingQueue Example

Let's take another classic example of coordination between two different tasks. Below is a simplified blocking queue example.

 import java.util.LinkedList;  
 import java.util.Queue;  
   
 public class SimpleBlockingQueue<T> {  
      private Queue<T> queue = new LinkedList<T>();  
      private int size;  
   
      public SimpleBlockingQueue(int size) {  
           this.size = size;  
      }  
      public synchronized void put(T element) throws InterruptedException {  
           while (queue.size() == size) {  
                wait();  
           }  
           queue.add(element);  
           notify();  
      }  
      public synchronized T take() throws InterruptedException {  
           while (queue.isEmpty()) {  
                wait();  
           }  
           T item = queue.remove();  
           notify();  
           return item;  
      }  
 }  

Note, Java provides a BlockingQueue API; prefer it instead of re-inventing the wheel. 

 

Some Important Points 

why wait and notify are part of the base class?
Sounds more like thread concepts but put in superclass, Object. This is required because these methods get called from the synchronized block/ method and they manipulate locks. And these locks are objects (in Java). 

sleep vs wait
The lock doesn't get released during the call of sleep but that's not the case with wait. If wait is called without any parameter, it can come out due to notify() or notifyAll(). If wait is called with parameter then it can respond to time out as well as notify()/notifyAll().
Also, wait is always called from synchronized block/method but sleep can be called within non-synchronized methods as well. 

what if wait/notify is called from non-synchronized block/method
The program will compile. But you will get a runtime exception, IllegalMonitorStateException. This basically means that task calling wait(), notify(), or notifyAll() MUST own the lock for the object.

prefer API provided by the language
Instead of writing code using wait/notify mechanism, prefer inbuilt Java API. 

---
keep coding !!!

No comments:

Post a Comment