In this article, we'll look at the wait() method to control thread, and the notify()/notifyAll() methods. These methods are defined in the base class java.lang.Object and, accordingly, the inheritance mechanisms that are in Java provide these methods to absolutely all classes. That is, when you create your own class and its objects, you can always call these methods.

How do the wait() and notify()/notifyAll() methods work?

  • wait(). in short, this method releases the monitor and puts the calling thread into a wait state until another thread calls the notify()/notifyAll() method;
  • notify(). Continues the work of a thread whose wait() method was previously called;
  • notifyAll() method resumes all threads that have previously had their wait() method called.
Now let's take a closer look at the wait() method. The Object class contains three options for this method:
  • public final native void wait(long timeoutMillis) throws InterruptedException; It causes the current thread to wait until it’s awakened. Usually it happens by being notified or interrupted, or until a certain amount of real time has elapsed.

  • public final void wait() throws InterruptedException. It is no coincidence that we wrote a method without parameters as the second one. In fact, if you look at its code, it refers to the first variant of the method, it just has the 0L argument.

  • public final wait​(long timeout, int nanos). Causes the current thread to wait until it is awakened, typically by being notified or interrupted, or until a certain amount of real time has elapsed.

The wait() method is intended to suspend the calling thread. What does it mean? These methods belong to the class. Based on the class, you create an object. Objects exist in some threads. That is, objects are created in some threads. In the thread in which this object works, if you call wait() in it, this will lead to the fact that this thread will stop. The object itself acts as a kind of monitor. What is it? It is clear that you can create different objects and all of them will contain the wait() method. There is an understanding of which object caused the stop of a particular thread. The thread stops and will wait as long as it is written in the parameter. And then it will start. This thread cannot start itself. To resume work, there are notify and notifyAll methods. A call to notify() or notifyAll() must play some other thread. With wait(), you can stop multiple threads, and start all threads with notifyAll(). If multiple threads were stopped and notify() was called, it's impossible to tell exactly which thread will resume this method. If there are no waiting threads on the wait() method, then nothing happens when notify() or notifyAll() is called. A thread can call the wait() or notify() methods on a particular object only if it currently has a lock on that object. wait(), notify(), and notifyAll() should only be called from synchronized code.

Wait() method example

Here we’ve got one of the most popular examples that illustrates how the method works. Let's say we have a store, a producer, and a consumer. The manufacturer transfers some products of production to the store, after which the consumer can take them. Let the manufacturer have to produce 8 goods, respectively, the consumer must buy them all. But at the same time, no more than 6 items can be in the warehouse at the same time. To solve this problem, we use the wait() and notify() methods. Let's define three classes: Market, Manufacturer and Client. The Manufacturer in the run() method adds 8 products to the Market object using its put() method. The client in the run() method in a loop calls the get method of the Market object to get these products. The put and get methods of the Market class are synchronized. To track the presence of goods in the Market class, we check the value of the item variable. The get() method for getting a product should only fire if there is at least one product. Therefore, in the get method, we check if the product is missing. If the item is not available, the wait() method is called. This method releases the Market object's monitor and blocks the get method until the notify() method is called on the same monitor. When an item is added in the put() method and notify() is called, the get() method gets the monitor. After that, our client receives an item. To do this, a message is displayed, and the value of the item is decremented. Finally, the notify() method call signals the put() method to continue. In the put() method, similar logic works, only now the put() method should work if there are no more than 6 products in the Market.

class Market {

   private int item = 0;

   public synchronized void get() {
       //here we use wait() method
       while (item < 1) {
           try {
               wait();
           }
           catch (InterruptedException e) {
           }
       }
       item--;
       System.out.println("A client has bought 1 item...");
       System.out.println("Items quantity in Market warehouse... " + item);
       notify();
   }

   public synchronized void put() {
       //here we use wait() method when the Warehouse is full
       while (item >= 6) {
           try {
               wait();
           }
           catch (InterruptedException e) {
           }
       }
       item ++;
       System.out.println("Manufacturer has added 1 more item...");
       System.out.println("Now there are " + item + " items in Warehouse" );
       notify();
   }
}

class Manufacturer implements Runnable {

   Market market;

   Manufacturer(Market market) {
       this.market = market;
   }


   public void run() {
       for (int i = 0; i < 8; i++) {
           market.put();
       }
   }
}

class Client implements Runnable {

   Market market;
   Client(Market market) {
       this.market = market;
   }
   public void run() {
       for (int i = 0; i < 8; i++) {
           market.get();
       }
   }
}
//wait() method test class
public class WaitTest {
   public static void main(String[] args) {

       Market market = new Market();
       Manufacturer manufacturer = new Manufacturer(market);
       Client client = new Client(market);
       new Thread(manufacturer).start();
       new Thread(client).start();
   }
}
Here, using wait() in the get() method, we are waiting for the Manufacturer to add a new item. And after adding, we call notify(), as if to say that one place has become free on the Warehouse, and you can add more. In the put() method, using wait(), we are waiting for the release of space on the Warehouse. After the space is free, we add the item, notify() starts the thread and the Client can pick up the item. Here is the output of our program:
Manufacturer has added 1 more item... Now there are 1 items in Warehouse Manufacturer has added 1 more item... Now there are 2 items in Warehouse Manufacturer has added 1 more item... Now there are 3 items in Warehouse Manufacturer has added 1 more item... Now there are 4 items in Warehouse Manufacturer has added 1 more item... Now there are 5 items in Warehouse Manufacturer has added 1 more item... Now there are 6 items in Warehouse A client has bought 1 item... Items quantity in Market warehouse... 5 A client has bought 1 item... Items quantity in Market warehouse... 4 A client has bought 1 item... Items quantity in Market warehouse... 3 A client has bought 1 item... Items quantity in Market warehouse... 2 A client has bought 1 item... Items quantity in Market warehouse... 1 A client has bought 1 item... Items quantity in Market warehouse... 0 Manufacturer has added 1 more item... Now there are 1 items in Warehouse Manufacturer has added 1 more item... Now there are 2 items in Warehouse A client has bought 1 item... Items quantity in Market warehouse... 1 A client has bought 1 item... Items quantity in Market warehouse... 0 Process finished with exit code 0