Object class contains the following three methods for inter-thread communication:
- wait
- notify
- notifyAll
wait() throws InterruptedException:It causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object.The wait() method releases the lock prior to waiting and reacquires the lock prior to returning from the wait() method.
There are two more flavours of wait method.
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. Here the timeout is mentioned in milliseconds.
wait(long timeout, int nanos) throws InterruptedException: Similar to the above method, but it allows finer control over the amount of time to wait for a notification before giving up. The amount of real time,measured in nanoseconds, is given by:1000000*timeout+nanos
If the current thread is interrupted by any thread before or while it is waiting, then an InterruptedException is thrown.
notify():It wakes up one single thread that called wait() on the same object. The choice of the thread to wake depends on the OS implementation of thread management.
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. So, if a notifier calls notify() on a resource but the notifier still needs to perform 10 seconds of actions on the resource within its synchronized block, the thread that had been waiting will need to wait at least another additional 10 seconds for the notifier to release the lock on the object, even though notify() had been called.
notifyAll(): Wakes up all threads that are waiting on this object’s monitor. A
thread waits on an object’s monitor by calling one of the wait methods.
The awakened threads will not be able to proceed until the current thread relinquishes the lock on this object. The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object.
Why wait(), notify() and notifyAll() must be called inside a synchronized method or block?
Every object created in Java has one associated monitor (mutually exclusive lock). Only one thread can own a monitor at any given time.For achieving synchronization in Java this monitor is used. When any thread enters a synchronized method/block it acquires the lock on the specified object. When any thread acquires a lock it is said to have entered the monitor. All other threads which need to execute the same shared piece of code (locked monitor) will be suspended until the thread which initially acquired the lock releases it.
Since wait method is about thread releasing the object’s lock where as notify/notifyAll methods are about notifying the thread(s) waiting for the object’s lock. So, wait(), notify() and notifyAll() methods should be invoked on an object only when the current thread has already acquired the lock on an object. This is possible only inside synchronized block or synchronized method.
If wait(), notify() and notifyAll() methods are invoked in a non synchronized context then it will result in java.lang.IllegalMonitorStateException.
Why wait(), notify() and notifyAll() are in Object class and not in Thread class in java?
- These methods works on the locks and locks are associated with Object and not Threads. Hence, it is in Object class.
- The threads have no specific knowledge of each other and they can run asynchronously. They run and they lock, wait, and notify on the object that they want to get access to. They have no knowledge of other threads and don’t need to know their status.
For better understanding why wait() and notify() method belongs to Object class, Let us take a real life example: Suppose a gas station has a single toilet, the key for which is kept at the service desk. The toilet is a shared resource for passing motorists. To use this shared resource the prospective user must acquire a key to the lock on the toilet. The user goes to the service desk and acquires the key, opens the door, locks it from the inside and uses the facilities.
Meanwhile, if a second prospective user arrives at the gas station he finds the toilet locked and therefore unavailable to him. He goes to the service desk but the key is not there because it is in the hands of the current user. When the current user finishes, he unlocks the door and returns the key to the service desk. He does not bother about waiting customers. The service desk gives the key to the waiting customer. If more than one prospective user turns up while the toilet is locked, they must form a queue waiting for the key to the lock. Each thread has no idea who is in the toilet.
Obviously in applying this analogy to Java, a Java thread is a user and the toilet is a block of code which the thread wishes to execute. Java provides a way to lock the code for a thread which is currently executing it using the synchronized keyword, and making other threads that wish to use it wait until the first thread is finished. These other threads are placed in the waiting state. Java is NOT AS FAIR as the service station because there is no queue for waiting threads. Any one of the waiting threads may get the monitor next, regardless of the order they asked for it. The only guarantee is that all threads will get to use the monitored code sooner or later.
Finally the answer to your question: the lock could be the key object or the service desk. None of which is a Thread.
However, these are the objects that currently decide whether the toilet is locked or open. These are the objects that are in a position to notify that the bathroom is open (“notify”) or ask people to wait when it is locked wait.
Java Example
Let us have a look into the famous Producer Consumer concurrency problem.We will make use of wait and notify methods to resolve the concurrency issue.
Here, the producer has to produce an product and notify the consumer to consume it. Before producing the next product the producer has to wait until the previously produced product is consumed by the consumer. Similarly before consuming, the consumer has to wait until an product is being produced. It has to consume the element as soon as it is available and notify the producer to produce the next product.
ProductManufacturer Class
package com.blog;
public class ProductManufacturer {
private Product product = null;
private boolean isProductAvailable = false;//Checks if already a product is available for consumption
public synchronized void consume() {
while (!isProductAvailable) {
try {
wait();
} catch (InterruptedException e) {
}
}
System.out.println(“Got Product:”+product.getProductName());
product = null;
isProductAvailable=false;
notify();
}
public synchronized void produce(String productName) {
while (isProductAvailable) {
try {
wait();
} catch (InterruptedException e) {
}
}
product = new Product(productName);
System.out.println(“Created Product:”+productName);
isProductAvailable=true;
notify();
}
public class Product {//Product inner class
private String productName;
public Product(String productName) {
this.setProductName(productName);
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
}
}
Producer class
package com.blog;
import java.util.List;
public class Producer implements Runnable {
private ProductManufacturer productManufacturer;
private List productNames;
public List getProductNames() {
return productNames;
}
public void setProductNames(List productNames) {
this.productNames = productNames;
}
@Override
public void run() {
for (String productName : productNames) {
productManufacturer.produce(productName);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public Producer(ProductManufacturer productManufacturer,
List productNames) {
this.productManufacturer = productManufacturer;
this.productNames = productNames;
}
}
Consumer Class
package com.blog;
public class Consumer implements Runnable {
private ProductManufacturer productManufacturer;
private int productSize;
@Override
public void run() {
for (inti = 0; i < productSize; i++) {
productManufacturer.consume();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public Consumer(ProductManufacturer productManufacturer, intproductSize) {
this.productManufacturer = productManufacturer;
this.productSize = productSize;
}
}
ProducerConsumerTest Class
package com.blog;
import java.util.ArrayList;
import java.util.List;
public class ProducerConsumerTest {
public static void main(String[] args) {
List productNames = new ArrayList();
productNames.add(“TV”);
productNames.add(“AC”);
productNames.add(“Car”);
productNames.add(“Bike”);
productNames.add(“Laptop”);
ProductManufacturer productManufacturer = new ProductManufacturer();
Producer producer = new Producer(productManufacturer, productNames);
Consumer consumer = new Consumer(productManufacturer,
productNames.size());
Thread t1 = new Thread(producer);
Thread t2 = new Thread(consumer);
t1.start();
t2.start();
}
}
Output
Created Product:TV
Got Product:TV
Created Product:AC
Got Product:AC
Created Product:Car
Got Product:Car
Created Product:Bike
Got Product:Bike
Created Product:Laptop
Got Product:Laptop