June 17, 2022

wait(), notify() And notifyAll() Methods in Java

The wait(), notify() and notifyAll() methods in Java are used for inter-thread communication. Each object in Java has an associated lock with it and the object whose lock is held (by the current thread) is used for communication among the threads. There are two important points about the wait(), notify() and notifyAll() methods in Java.

1- These methods are implemented in Object class as final methods. Since Object class is super class of all the classes in Java so wait(), notify() and notifyAll() methods are available in all the classes.

wait(), notify() and notifyAll() methods in Object class are declared as below-

  • public final void wait() throws InterruptedException
  • public final void notify()
  • public final void notifyAll()

Refer Why wait(), notify() And notifyAll() Methods Are in Object Class to know the reason for putting these methods in Object class.

2- wait(), notify() and notifyAll() methods must be called within a synchronized method or block. If you call these methods within a method that’s not synchronized, the program will compile, but when you run it IllegalMonitorStateException will be thrown.

Refer Why wait(), notify() and notifyAll() Methods Must be Called From a Synchronized Method or Block to know the reason why these methods are to be called with in a synchronized context.

For example, in the following code wait() method is called out side of the synchronized block, code will compile but at run time IllegalMonitorStateException will be thrown.

public void increment(){
  synchronized(this){
    for(int i = 1; i <= 5 ; i++){
      System.out.println(Thread.currentThread().getName() + " i - " + i);
    }
  }
  try {
    this.wait();
  } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
  }
}
Output
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Unknown Source)
at com.knpcode.Counter.increment(SynchronizedDemo.java:10)
at com.knpcode.SynchronizedDemo$1.run(SynchronizedDemo.java:31)

You can call wait(), notify() and notifyAll() methods on the object whose lock has been used to enter the synchronized context. If you use any other object then also code will compile but IllegalMonitorStateException will be thrown at run time.

wait() method in Java

wait() method causes the current thread to place itself into waiting state. Here current thread means the thread currently executing with in the synchronized context and owns this object's monitor lock.

wait() method in the Object class is overloaded and has three variants.

  • final void wait()- Causes the current thread to wait until another thread calls notify or notifyAll method or the thread is interrupted.
  • final void wait(long timeout)- Causes the current thread to wait until another thread calls notify or notifyAll method, the thread is interrupted or the maximum time to wait (in milliseconds) has expired.
  • final void wait(long timeout, int nanos)- Causes the current thread to wait until another thread calls notify or notifyAll method, the thread is interrupted or the maximum time to wait (in milliseconds) plus additional time in nanoseconds has expired.

notify() method in Java

Wakes up a single thread that is waiting on this object's monitor. If there are more than one thread waiting on this object any one of them is arbitrarily chosen to be awakened.

The awakened thread will not be able to proceed until the current thread relinquishes the lock on this object. If any other threads are trying to acquire the lock on this object to enter the synchronized method or block then the awakened thread also compete with them in usual manner with no special advantage or any disadvantage.

notifyAll() method in Java

Wakes up all threads that are waiting on this object's monitor rather than a single thread. The awakened threads will not be able to proceed until the current thread relinquishes the lock on this object. Again these awakened threads have to compete with any other threads trying to acquire lock on this object.

Spurious wakeup

In very rare cases a waiting thread can wake up without being notified, interrupted, or timing out this is known as spurious wakeup. Applications must guard against it by putting a call to wait() within a loop that checks the condition on which the thread is waiting.

synchronized (obj) {
  while (<condition does not hold> and <timeout not exceeded>) {
    long timeout = ... ; // recompute timeout values
    int nanos = ... ;
    obj.wait(timeout, nanos);
  }
  ... // Perform action appropriate to condition or timeout
}

Reference- https://docs.oracle.com/javase/10/docs/api/java/lang/Object.html#wait(long,int)

Java wait, notify, notifyAll example

A very good example to show wait(), notify() And notifyAll() methods in practice is implementing a producer consumer using two threads. Here producer thread produces a message and put it in a list, during that period consumer thread should wait. Once there is a message in a list consumer thread should be notified. Same way producer thread should be in waiting state when consumer thread is consuming the message and should be notified to put another message in the list when the current message is consumed. List object is the shared object here on which wait and notify methods should be called.

import java.util.ArrayList;
import java.util.List;
// This class produces message and puts it in a shared list
class ProduceMsg implements Runnable{
  List<String> msgObj;
  ProduceMsg(List<String> msgObj){
    this.msgObj = msgObj;
  }
  @Override
  public void run() {
    // running it 5 times
    for(int i = 1; i <= 5; i++){
      synchronized (msgObj) {
        // loop checking wait condition to avoid spurious wakeup
        while(msgObj.size() >= 1){
          try {
            msgObj.wait();
          } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
          }
        }                
        msgObj.add("Hello-" + i);
        System.out.println("Adding to list - " + msgObj.get(0));
        msgObj.notify();  
      }
    }
  }
}

// This class consumes message from a shared list
class ConsumeMsg implements Runnable{
  List<String> msgObj;
  ConsumeMsg(List<String> msgObj){
    this.msgObj = msgObj;
  }
  @Override
  public void run() {
    for(int i = 1; i <= 5; i++){
      synchronized (msgObj) {
        // loop checking wait condition to avoid spurious wakeup
        while(msgObj.size() < 1){
          try {
            msgObj.wait();
          } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
          }                    
        }
        // Getting value from the list
        System.out.println("Getting from queue - " + msgObj.get(0));
        msgObj.remove(0);
        msgObj.notify();         
      }
    }
  }	
}
public class InterTDemo {
  public static void main(String[] args) {
    List<String> msgObj = new ArrayList<String>();
    // Creating Producer thread
    Thread t1 = new Thread(new ProduceMsg(msgObj));
    // Creating Consumer thread
    Thread t2 = new Thread(new ConsumeMsg(msgObj));
    t1.start();
    t2.start();
  }
}
Output
Adding to list - Hello-1
Getting from queue - Hello-1
Adding to list - Hello-2
Getting from queue - Hello-2
Adding to list - Hello-3
Getting from queue - Hello-3
Adding to list - Hello-4
Getting from queue - Hello-4
Adding to list - Hello-5
Getting from queue – Hello-5

If you don’t use wait() and notify() methods here for inter thread communication and just synchronize on the shared object, still only one of the thread will acquire the lock but nothing stops the thread to execute more than once after entering the synchronized block. You can run the same example by commenting the code for wait and notify methods.

// This class produces message and puts it in a shared list
class ProduceMsg implements Runnable{
  List<String> msgObj;
  ProduceMsg(List<String> msgObj){
    this.msgObj = msgObj;
  }
  @Override
  public void run() {
    // running it 5 times
    for(int i = 1; i <= 5; i++){
      synchronized (msgObj) {
        // loop checking wait condition to avoid spurious wakeup
         /* while(msgObj.size() >= 1){
          try {
            msgObj.wait();
          } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
          }
        }*/
                
        msgObj.add("Hello-" + i);
        System.out.println("Adding to list - " + msgObj.get(0));
        msgObj.notify();  
      }
    }
  }
}

// This class consumes message from a shared list
class ConsumeMsg implements Runnable{
  List<String> msgObj;
  ConsumeMsg(List<String> msgObj){
    this.msgObj = msgObj;
  }
  @Override
  public void run() {
    for(int i = 1; i <= 5; i++){
      synchronized (msgObj) {
  /*	     // loop checking wait condition to avoid spurious wakeup
              while(msgObj.size() < 1){
                  try {
                    msgObj.wait();
                  } catch (InterruptedException e) {
                      // TODO Auto-generated catch block
                      e.printStackTrace();
                  }                    
         }*/
        // Getting value from the list
        System.out.println("Getting from queue - " + msgObj.get(0));
        msgObj.remove(0);
        msgObj.notify();         
      }
    }
  }	
}
Output
Adding to list - Hello-1
Getting from queue - Hello-1
Exception in thread "Thread-1" 
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0 at java.util.ArrayList.rangeCheck(Unknown Source)
	at java.util.ArrayList.get(Unknown Source)
	at com.knpcode.ConsumeMsg.run(InterTDemo.java:54)
	at java.lang.Thread.run(Unknown Source)
Adding to list - Hello-2
Adding to list - Hello-2
Adding to list - Hello-2
Adding to list – Hello-2

As you can see here, consumer thread tries to get another message from the list after consuming one message as nothing stops it to keep executing once it is in the synchronized block. This results in IndexOutOfBoundsException as list is already empty.

That's all for the topic wait(), notify() And notifyAll() Methods in Java. If something is missing or you have something to share about the topic please write a comment.


You may also like

No comments:

Post a Comment