In Java, communication between threads can be implemented by combining the wait() and notify() or notifyAll() methods of calling objects. Calling wait() in a thread blocks waiting for notify() or notifyAll() from another thread. Calling notify() or notifyAll() in a thread blocks notifyAll(). The other threads are notified to return from the wait() method.

Object is the superclass of all classes and has five methods that make up the core of the wait/notification mechanism: notify(), notifyAll(), wait(), wait(long), and wait(long, int). In Java, all classes inherit from Object, so all classes have these common methods to work with. Also, because they are all declared final, neither method can be overridden ina subclass.

wait()

public final void wait() throws InterruptedException, IllegalMonitorStateException

This method is used to put the current thread into hibernation until it is notified or interrupted. Before calling wait(), the thread must acquire an object-level lock on the object, meaning that the wait() method can only be called in synchronized methods or synchronized blocks. After entering the wait() method, the current thread releases the lock. If calling wait (), not hold the appropriate lock, it throws IllegalMonitorStateException, it is a subclass RuntimeException, therefore, don’t need a try-catch structure. The thread wakes up and picks up where the wait() method was last called. Wait () is called in a loop, and a process notifying () from wait() needs to check again to see if it meets the execution criteria. If it does not, it must wait() again before proceeding

notify()

public final native void notify() throws IllegalMonitorStateException

The method also to call in the synchronized methods and synchronized block, namely before the call, the thread must also obtain the object object level lock, if the call of the notify () without holding the appropriate lock, also sell IllegalMonitorStateException.

This method is used to notify other threads that may be waiting for an object lock on the object. If more than one thread is waiting, the thread planner selects any thread in the wait() state to notify it and make it wait for the object lock on the object. The lock is not released until the synchronized block exits, and the synchronized thread can acquire the lock) without alerting other threads that are also waiting to be notified. When the first wait thread to hold the object lock completes, it releases the lock. If the object does not use notify again, other threads in the wait state will continue to block even if the object is idle. Until the object emits a notify or notifyAll. Note here: they wait to be notified or notifyAll, not locked. This is different from what happens after notifyAll() is executed below.

notifyAll()

public final native void notifyAll() throws IllegalMonitorStateException

This method works in the same way as notify(), with one important difference:

NotifyAll Changes the status of all threads waiting for an object to exit the wait state (that is, all threads are awakened and are no longer waiting for notify or notifyAll, but cannot continue to execute because they have not obtained the lock on the object). Once the object lock is released (when the notifyAll thread exits the synchronized block that invokes notifyAll), they compete. If one thread acquires the object lock, it continues to execute, and after it exits the synchronized block and releases the lock, other awakened threads continue to compete for the lock until all awakened threads have completed their execution.

Wait (long) and wait (long, int)

These two methods are used to set the waiting timeout time. The latter adds NS to the timeout time, which is difficult to achieve accuracy. Therefore, this method is rarely used. For the former, if the specified number of milliseconds has elapsed before the waiting thread is notified or interrupted, it races to regain the lock and returns from wait(long). Also, it is important to note that if timeout is set, when wait() returns, we cannot be sure whether it has been notified or timed out, since the wait() method does not return any relevant information. This can be done by setting the flag bit before notify and reading the flag bit after wait(). To ensure that notify is not omitted, we need another flag bit to loop through the wait() method.

Producer-consumer example Describes how to use wait, notify, and notifyAll

public class ProducerConsumerInJava { public static void main(String args[]) { Queue<Integer> buffer = new LinkedList<>(); int maxSize = 10; Thread producer = new Producer(buffer, maxSize, "PRODUCER"); Thread consumer = new Consumer(buffer, maxSize, "CONSUMER"); producer.start(); consumer.start(); } } /** * Producer Thread will keep producing values for Consumer * to consumer. It will use wait() method when Queue is  full * and use notify() method to send notification to Consumer * Thread. */ class Producer extends Thread { private Queue<Integer> queue; private int maxSize; public Producer(Queue<Integer> queue, int maxSize, String name) { super(name); this.queue = queue; this.maxSize = maxSize; } @Override public void run() { while (true) { synchronized (queue) { while (queue.size() == maxSize) { try { System.out.println("Queue is full, " + "producer thread waiting for " + "consumer to take something from queue"); queue.wait(); } catch (Exception ex) { ex.printStackTrace(); } } Random random = new Random(); int i = random.nextInt(); System.out.println("Producing value : " + i); queue.add(i); queue.notifyAll(); } } } } } /** * Consumer Thread will consumer values form shared queue. * It will also use wait() method to wait if queue is * empty. It will also use notify method to send * notification to producer thread after consuming values * from  queue. */ class Consumer extends Thread { private Queue<Integer> queue; private int maxSize; public Consumer(Queue<Integer> queue, int maxSize, String name){ super(name); this.queue = queue; this.maxSize = maxSize; } @Override public void run() { while (true) { synchronized (queue) { while (queue.isEmpty()) { System.out.println("Queue is empty, " + "consumer thread is waiting" + " for producer thread to put something in queue"); try { queue.wait(); } catch (Exception ex) { ex.printStackTrace(); } } System.out.println("Consuming value : " + queue.remove()); queue.notifyAll(); } } } } }Copy the code

conclusion

If a thread calls an object’s wait() method, it is in that object’s wait pool, and the threads in the wait pool do not compete for the lock on that object

When a thread invokes the notifyAll() or notify() methods of an object, the awakened thread enters the lock pool of the object, and the threads compete for the lock

A thread with a higher priority has a higher probability of competing for an object lock. If a thread does not compete for the object lock, it will remain in the lock pool and only return to the wait pool if the thread calls wait() again. The thread that contended for the object lock continues to execute until the synchronized block is finished, which releases the object lock, at which point the pool of threads continues to contend for the object lock

Forever in synchronized object function or use of wait, notify, or notifyAll, or you’ll throw IllegalMonitorStateException

Always use wait in a while loop instead of an if statement. Thus, the loop checks the wait condition both before and after the thread sleeps, and handles the wake notification if the condition has not actually changed. (With if, the wake thread starts from the code after the wait, but does not rejudge the if condition and continues to run the code after the if block.) If a while is used, the loop condition will be reevaluated, and if not, the loop condition will be executed. If so, the loop will continue to wait.)

Always use wait on objects shared between multiple threads (in the producer-consumer model, buffer queues)

NotifyAll () is generally preferred to notify()