Java Thread : wait() , notify() and notifyAll()

Category : Java | Sub Category : Java Threads | By Prasad Bonam Last updated: 2021-04-20 04:04:35 Viewed : 535


Java Thread : wait() , notify() and notifyAll()

In multi-threaded environment,  the wait()notify() and notifyAll() methods are used for synchronizing and coordinating the access for the shared resource among threads.  These methods should be called in a synchronized context.

Important things to be noted!

  • wait(), notify() and notifyAll() must be called inside the synchronized context. otherwise IllegalMonitorStateException will be thrown.
  • These  methods are derived from the Object class. Not from the Thread class.

wait(), notify() and notifyAll() methods are derived from Object class. Not from Thread class

wait(), notify() and notifyAll() methods are derived from the Object class. Since these methods are frequently used in multithreaded environments with threads, most of the people have misunderstand that these methods are derived from Thread class. Since it is retrieved from the Object class, any object/instance created in java can be synchronized.

 

Lets look at a real world example. Here is the code and explanation.

import java.util.LinkedList;
import java.util.Queue;
public class Factory
{
private static Queue<String> sharedQueue = new LinkedList<>();
public static void main(String[] args)
{
System.out.println("factory started ");
Producer producer = new Producer(sharedQueue);
producer.start();
Consumer consumer = new Consumer(sharedQueue);
consumer.start();
try {
System.out.println("main thread wait until the consumer completes ");
synchronized (consumer) {
consumer.wait();
}
} catch (InterruptedException e) {
System.out.println("sleeping thread get interrupted ");
}
System.out.println("main thread completed ");
}
}

  
 

 

You can see that the Factory class has created and started two thread objects known as Producer and Consumer. Both these thread objects share a common queue for their operations. In last, the main thread wait until the Consumer thread calls the notify() method on the consumer thread itself. Now both thread has been started and lets look at their execution.

 

class Producer extends Thread
{
private Integer numOfItemsToBeProduced = 10;
private int maxQueueSize = 5;
private Queue<String> sharedQueue;
Producer(Queue<String> sharedQueue)
{
this.sharedQueue = sharedQueue;
}
public void run()
{
synchronized (sharedQueue) {
while (sharedQueue.size() <= maxQueueSize && numOfItemsToBeProduced != 0)
{
String item = "item " + numOfItemsToBeProduced;
System.out.println("producing " + item);
sharedQueue.add(item);
numOfItemsToBeProduced;
if (sharedQueue.size() == maxQueueSize) {
System.out.println("queue is full. notifying the consumer ");
try {
Thread.sleep(2000);
sharedQueue.notify();
sharedQueue.wait();
} catch (InterruptedException e) {
System.out.println("sleeping thread get interrupted ");
}
}
}
sharedQueue.notifyAll();
System.out.println("Producer Completed (Nothing to be produced) : remaining items ["+numOfItemsToBeProduced+"]");
}
synchronized (this) {
//notify all the threads that are waited on the Producer
notifyAll();
}
}
}

  

 

class Consumer extends Thread
{
private Integer numOfItemsToBeConsumed = 10;
private Queue<String> sharedQueue;
Consumer(Queue<String> sharedQueue)
{
this.sharedQueue = sharedQueue;
}
public void run()
{
System.out.println("consumer started ");
synchronized (sharedQueue) {
while (!sharedQueue.isEmpty() && numOfItemsToBeConsumed != 0) {
System.out.println("consuming " + sharedQueue.poll());
numOfItemsToBeConsumed;
if (sharedQueue.isEmpty()) {
try {
System.out.println("consumer waits till producer produces items ");
Thread.sleep(2000);
sharedQueue.notify();
sharedQueue.wait();
} catch (InterruptedException e) {
System.out.println("Consumer: waiting thread interrupted ");
}
}
}
//notifying all the threads that are waiting on the shared queue
sharedQueue.notifyAll();
System.out.println("Consumer Completed (Nothing to be consumed) : remaining items ["+numOfItemsToBeConsumed+"]");
}
synchronized (this) {
//notify all the threads that are waited on the Consumer
notifyAll();
}
}
}

 
 

 

The Producer thread is responsible for producing the item and the consumer thread is responsible for consuming the items. As you can see that the business logic of both producer and consumer has been placed in a synchronized blocks. Both synchronized blocks have been synchronized with sharedQueue. Therefore in order to complete their execution, the running thread should have the the object monitor/lock of the sharedQueue.

If the object monitor of the shared queue is first acquired by the Producer thread, the Consumer thread has to wait until it is released by the Producer thread.

If the object monitor of the shared queue is first acquired by the Consumer thread, the Producer thread has to wait until it is released by the Consumer thread.

The producer stores the produced items in the sharedQueue and its max size is considered as 5.  when the queue size is become 5, the Producer thread release the object monitor of the sharedQueue and allow any other thread to continue/resume its works with sharedQueue. This is done by calling the notify()  method on the sharedQueue.

notify() –  notify an already waited thread on sharedQueue about releasing the monitor/lock of the shared queue. Once the notify() is called,  it will not release the lock/monitor immediately. it will release the monitor once the synchronized block is completed.

wait() – this will immediately release the object monitor/lock of the shared queue and the currently running thread  hold/sleep until some other thread calls the notify method on the same object.

Once Producer thread invokes the wait() on the sharedQueue, the monitor  of the shared queue is released and currently running thread (Producer thread) goes to the sleep state. The released monitor/lock of the shared queue is available for some other waiting thread to acquire. In this case, it will be acquired by the Consumer thread.

Now the Producer thread is waiting until some other thread calls the notify(() method and release the monitor of the sharedQueue. Consumer thread has acquired the monitor and continue its work with it.

 

The Consumer thread will again consumes all the items in the queue until the queue is empty. when the queue is empty, it invokes the notify() method on the sharedQueue to notify one of the waiting thread about releasing the object monitor. Now the producer thread is aware  and ready to acquire the object monitor once it is released by the Consumer thread. Consumer thread will invoke wait() method for waiting until producer produced items and calls the notify() on the sharedQueue.

The access for the sharedQueue will be properly synchronized between Producer and Consumer until both of thread completes their execution. once the execution is over, each method will call their notify methods to release their object monitor and acknowledge some other threads that are waiting on them.

Can you remind that the main thread is waiting for the Consumer thread. so once the notify method of the Consumer thread is invoked, the main thread will be resumed.

You can run the program and see the output as follows.

factory started 
producing item 10
producing item 9
producing item 8
producing item 7
producing item 6
queue is full. notifying the consumer 
main thread wait until the consumer completes 
consumer started 
consuming item 10
consuming item 9
consuming item 8
consuming item 7
consuming item 6
consumer waits till producer produces items 
producing item 5
producing item 4
producing item 3
producing item 2
producing item 1
queue is full. notifying the consumer 
consuming item 5
consuming item 4
consuming item 3
consuming item 2
consuming item 1
consumer waits till producer produces items 
Producer Completed (Nothing to be produced) : remaining items [0]
Consumer Completed (Nothing to be consumed) : remaining items [0]
main thread completed

 

Now lets look at  the functionality of each method.

wait()

if the wait() is called on a object, the currently running thread will release the monitor/lock of that object and go to sleep until some other thread calls the notify() method on the same object.

e.g: –

synchronized(objectA){
      objectA.wait();
}

 

The currently running thread will release the monitor of the objectA and go to sleep until some other thread invokes the notify() or notifyAll() method on the objectA.

 

notify()

It wakes up one single thread that called wait() on the same object. It should be noted that calling notify() does not actually give up a lock on a resource. It tells a waiting thread that that thread can wake up. However, the lock is not actually given up until the notifier’s synchronized block has completed. The selection of the notifying thread will be  decided by the thread scheduler.

 

notifyAll()

It wakes up all the threads that called wait() on the same object. The highest priority thread will run first in most of the situation, though not guaranteed.

 

Important

if the wait() and notify()/notifyAll() are used together, the best way to call the notify()/notifyAll() first and then wait() method.  if the notify() is called after the wait() method, then the notify() will not be invoked because the currently running thread will go to the sleep state immediately.

Search
Related Articles

Leave a Comment: