Friday, April 8, 2022

Inter-thread Communication Using wait(), notify() And notifyAll() in Java

Java provides inter-thread communication using the following methods of the Object class.

  • wait()
  • notify()
  • notifyAll()

wait(), notify() and notifyAll() methods in Java

These methods wait(), notify() and notifyAll() are implemented as final method in the Object class thus available to all the classes, as Object class is the super class of all the classes in Java.

Another important point about wait(), notify() and notifyAll() methods in Java is that they can only be called from a synchronized context, as these methods are about releasing the monitor and acquiring it again. Threads acquire monitor(lock) when entering a synchronized method (or block) so it makes sense to call them from synchronized context.

Refer Why wait(), notify() And notifyAll() Must be Called Inside a Synchronized Method or Block to see why these methods must be called from a synchronized context.


wait() method in Java

Wait method tells the current thread (thread which is executing code inside a synchronized method or block) to give up monitor and go to sleep, until another thread invokes the notify() or notifyAll() method for this object.

General form of wait method in Java

public final void wait() throws InterruptedException

There are two more overloaded wait methods

public final void wait(long timeout) throws InterruptedException

Causes the current thread to wait until either another thread invokes the notify() method or the notifyAll() method for this object, or a specified amount of time has elapsed.

public final void wait(long timeout, int nanos) throws InterruptedException

Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object, or some other thread interrupts the current thread, or a certain amount of real time has elapsed.

Generally you'll use code similar to as given below for calling wait method.

synchronized (obj) {
  while (condition not true){
    obj.wait();
  }
}

notify() method in Java

Wakes up a single thread that is waiting on this object's monitor. If more than one threads are waiting on this object, one of them is chosen to be awakened. The choice is arbitrary and occurs at the discretion of the implementation.

Note that the thread which comes out of waiting because of the notify() method will not be able to proceed until the current thread relinquishes the lock on this object. The awakened thread just changes to the runnable state and it is ready to be scheduled again. The awakened thread will compete in the usual manner with any other threads that might be actively competing to synchronize on this object. The awakened thread enjoys no reliable privilege or disadvantage in being the next thread to lock this object.

When awakened thread(thread which has come out of waiting because of notify() method) gains control of the object, all its synchronization claims on the object are restored to the situation as of the time that the wait method was invoked that is on return from the wait method, the synchronization state of the object and of thread is exactly as it was when the wait method was invoked.

Generally you'll use code similar to as given below for calling notify method.

synchronized (obj) {
  while (condition not true){
    obj.wait();
  }
  // When condition holds true
  ..
  ..
  obj.notify();
}

notifyAll() method in Java

Wakes up all the threads that called wait() on the same object. As explained in notify() any one of the threads will be granted access to the object.

Generally you'll use code similar to as given below for calling notifyAll method.

synchronized (obj) {
  while (condition not true){
    obj.wait();
  }
  // When condition holds true
  ..
  ..
  obj.notifyAll();
}

What is Spurious Wakeup

Once wait is called on an object the thread that is currently executing with in the synchronized context waits until notify() or notifyAll() method is called. But there is a possibility that a waiting thread resumes again even when notify() or notifyAll() are not called (this will rarely occur in practice). This is known as spurious wakeup in Java.

To guard against spurious wakeup the recommendation is that call to wait() method should be with in a loop that checks the condition on which the thread is waiting.

synchronized (obj) {
  while (condition does not hold)
    obj.wait(timeout);
    ... // Perform action appropriate to condition
}

Read more about spurious wakeup in the wait method of the Java docs of Object class- http://docs.oracle.com/javase/8/docs/api/java/lang/Object.html

Producer-Consumer example using wait, notify in Java

Let's see one of the oft mentioned example of producer and consumer implemented with two threads, where producer thread produces a number, puts it in a list and then consumer thread gets that number out of the list. Since a shared list is used between these two threads, so to make sure that both threads work in tandem wait() and notify() methods are used for inter-thread communication.

class Producer implements Runnable{
  List<Integer> sharedListObj;
  Producer(List<Integer> sharedListObj){
    this.sharedListObj = sharedListObj;
  }
  @Override
  public void run() {
    int i = 0;
    while(true){
      synchronized (sharedListObj) {
        // While condition as mandated to avoid spurious wakeup
        while(sharedListObj.size() >= 1){
          try {
            sharedListObj.wait();
          } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
          }
        }
        // Putting value in the list
        System.out.println("Adding to queue - " + Thread.currentThread().getName() + " " + ++i);
        sharedListObj.add(i);
        sharedListObj.notify();    
        // To get out of while(true) loop, putting
        // only 5 values
        if(i > 4) break;
      }
    }
  }            
}

class Consumer implements Runnable{
  List<Integer> sharedListObj;
  Consumer(List<Integer> sharedListObj){
    this.sharedListObj = sharedListObj;
  }
  @Override
  public void run() {    
    while(true){
      synchronized (sharedListObj) {
        while(sharedListObj.size() < 1){
          try {
            sharedListObj.wait();
          } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
          }                    
        }
        // Getting value from the list
        System.out.println("Getting from queue " + Thread.currentThread().getName() + " " + sharedListObj.get(0));
        // To get out of while(true) loop
        if(sharedListObj.get(0) == 5) break;
        sharedListObj.remove(0);
        sharedListObj.notify();                    
      }
    }
  }
}


public class InterThreadDemo {
  public static void main(String[] args) {
    // This is the shared list shared between producer
    // and consumer
    List<Integer> sharedListObj = new ArrayList<Integer>();
    Thread t1 = new Thread(new Producer(sharedListObj), "Producer");
    Thread t2 = new Thread(new Consumer(sharedListObj), "Consumer");
    t1.start();
    t2.start();    
  }
}

Output

Adding to queue - Producer 1
Getting from queue Consumer 1
Adding to queue - Producer 2
Getting from queue Consumer 2
Adding to queue - Producer 3
Getting from queue Consumer 3
Adding to queue - Producer 4
Getting from queue Consumer 4
Adding to queue - Producer 5
Getting from queue Consumer 5

If wait/notify related code is commented though the access is synchronized nothing stops producer thread to keep producing numbers or consumer for keep on consuming numbers. Since I have used a list here and trying to get 0th element from the list it may even lead to ArrayIndexOutofBoundsException.

class Producer implements Runnable{
  List<Integer> sharedListObj;
  Producer(List<Integer> sharedListObj){
    this.sharedListObj = sharedListObj;
  }
  @Override
  public void run() {
    int i = 0;
    while(true){
      synchronized (sharedListObj) {
        // While condition as mandated to avoid spurious wakeup
        /*while(sharedListObj.size() >= 1){
            try {
              sharedListObj.wait();
            } catch (InterruptedException e) {
              // TODO Auto-generated catch block
              e.printStackTrace();
            }
        }*/
        // Putting value in the list
        System.out.println("Adding to queue - " + Thread.currentThread().getName() + " " + ++i);
        sharedListObj.add(i);
        //sharedListObj.notify();    
        // To get out of while(true) loop
        if(i > 4) break;
      }
    }
  }            
}

class Consumer implements Runnable{
  List<Integer> sharedListObj;
  Consumer(List<Integer> sharedListObj){
    this.sharedListObj = sharedListObj;
  }
  @Override
  public void run() {    
    while(true){
      synchronized (sharedListObj) {
        /*while(sharedListObj.size() < 1){
          try {
            sharedListObj.wait();
          } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
          }                    
        }*/
        // Getting value from the list
        System.out.println("Getting from queue " + Thread.currentThread().getName() + " " + sharedListObj.get(0));
        // To get out of while(true) loop
        if(sharedListObj.get(0) == 5) break;
        sharedListObj.remove(0);
        //sharedListObj.notify();        
                
      }
    }
  }
}

public class InterThreadDemo {
  public static void main(String[] args) {
    // This is the shared list shared between producer
    // and consumer
    List<Integer> sharedListObj = new ArrayList<Integer>();
    Thread t1 = new Thread(new Producer(sharedListObj), "Producer");
    Thread t2 = new Thread(new Consumer(sharedListObj), "Consumer");
    t1.start();
    t2.start();    
  }
}

Output

Adding to queue - Producer 1
Adding to queue - Producer 2
Adding to queue - Producer 3
Adding to queue - Producer 4
Exception in thread "Consumer" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
Getting from queue Consumer 1
Getting from queue Consumer 2
Getting from queue Consumer 3
Getting from queue Consumer 4
Adding to queue - Producer 5

 at java.util.ArrayList.rangeCheck(ArrayList.java:653)
 at java.util.ArrayList.get(ArrayList.java:429)
 at org.netjs.examples.Consumer.run(InterThreadDemo.java:55)
 at java.lang.Thread.run(Thread.java:745)

It can be seen, how, in absence of proper inter-thread communication using wait(), notify(), notifyAll() methods, nothing stops producer thread from keep on producing numbers on the other hand when the consumer thread gets hold of the synchronized block code it keeps on consuming numbers.

Refer Print odd-even numbers using threads and wait-notify to see how to print odd-even numbers using wait notify.

That's all for this topic Inter-thread Communication Using wait, notify And notifyAll - Java Multithreading. If you have any doubt or any suggestions to make please drop a comment. Thanks!


Related Topics

  1. Race Condition in Java Multi-Threading
  2. Deadlock in Java Multi-Threading
  3. Volatile Keyword in Java With Examples
  4. Java ThreadLocal Class With Examples
  5. Java Multithreading Interview Questions And Answers

You may also like-

  1. Initializer Block in Java
  2. Constructor Chaining in Java
  3. Varargs (Variable-length Arguments) in Java
  4. Functional Interfaces in Java
  5. Try-With-Resources in Java With Examples
  6. final Vs finally Vs finalize in Java
  7. Garbage Collection in Java
  8. Find All Permutations of a Given String Java Program

No comments:

Post a Comment