Detailed explanation of wait notify and notifyAll in Java multithreading

  • 2021-07-26 07:38:07
  • OfStack

Basic knowledge

First of all, we need to know that these are methods of Object objects. In other words, all objects in Java have these methods.


public final native void notify();
public final native void notifyAll();
public final native void wait(long timeout) throws InterruptedException;
public final void wait() throws InterruptedException {
  wait(0);
}

Where notify (), notifyAll (), wait (long timeout) are local methods

Secondly, we need to know that these methods are mainly used for communication between threads. Some people may ask, since they are used to communicate between threads, why are these methods not on the thread class Thread? For this question, let's first look at the specific use of these methods before answering this question.

Usage

Java states that the current thread must acquire the object lock when the caller has 3 methods. Therefore, it must be used with synchronized keyword


// Using patterns does not mean that code can be run 
synchronized(object) {
  while(contidion) {
    object.wait();
  }
  //object.notify();
  //object.notifyAll();
}

Or


public synchronized void methodName() {
  while(contidion) {
    object.wait();
  }
  //object.notify();
  //object.notifyAll();
}

After synchronized takes the object lock, the object lock must be held in the synchronized code block or method, so you can use wait () or notify ().

Through the above usage methods, we can also understand why these methods are on Object instead of Thread. Because each object can be an synchronized locked object, wait, notify, and so on must be associated with the object to be used with synchronized.

Action

方法 作用
wait 线程自动释放占有的对象锁,并等待notify。
notify 随机唤醒1个正在wait当前对象的线程,并让被唤醒的线程拿到对象锁
notifyAll 唤醒所有正在wait当前对象的线程,但是被唤醒的线程会再次去竞争对象锁。因为1次只有1个线程能拿到锁,所有其他没有拿到锁的线程会被阻塞。推荐使用。

1. The wait (), notify/notifyAll () methods are native to Object and cannot be overridden.

2. wait () blocks the current thread, provided that the lock must be acquired first, which is used in conjunction with synchronized keyword, that is, wait (), notify/notifyAll () methods are used in synchronized synchronization code block.

3. Because wait () and notify/notifyAll () are executed in synchronized code block, it shows that the current thread 1 must have acquired the lock.

When the thread executes the wait () method, it releases the current lock and then releases CPU into a wait state.

Only when notify/notifyAll () is executed will one or more of the waiting threads be awakened and continue until the synchronized block is executed or wait () is encountered halfway through, releasing the lock again.

That is, the execution of notify/notifyAll () only wakes up the sleeping thread, and does not immediately release the lock, which depends on the specific execution of the code block. Therefore, in programming, try to exit the critical area immediately after using notify/notifyAll () to wake up other threads

4. wait () needs to be surrounded by try catch, and interrupts can also wake up the waiting thread of wait.

5. The order of notify and wait cannot be wrong. If A thread executes notify method first and B thread executes wait method, B thread cannot wake up.

6. The difference between notify and notifyAll

The notify method wakes up only one thread waiting (for an object) and causes that thread to start executing. So if there are multiple threads waiting for an object, this method will only wake up one of them, depending on the operating system's implementation of multithreaded management. notifyAll wakes up all threads waiting (for objects), although which thread will be the first to process depends on the implementation of the operating system. The notifyAll method is recommended if multiple threads need to be awakened in the current situation. For example, when used in producer-consumer, it is necessary to wake up all consumers or producers every time to judge whether the program can continue to execute.

7. To test the change of a condition in multithreading, use if or while?

Note that after notify wakes up a sleepy thread, the thread will continue to execute after the last execution. Therefore, when judging the conditions, the wait statement can be ignored first. Obviously, to ensure that the program 1 must be executed, and to ensure that the program will not be executed until the conditions of 1 are met, while should be used to execute to ensure that the conditions are met and 1 is executed. The following code:


public class K {
  // State lock 
  private Object lock;
  // Conditional variable 
  private int now,need;
  public void produce(int num){
    // Synchronization 
    synchronized (lock){
      // Some of them do not meet the needs at present, so wait 
      while(now < need){
        try {
          // Waiting for blocking 
          wait();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        System.out.println(" I'm awakened! ");
      }
      //  Do other things 
    }
  }
}

Obviously, threads can only go down if the current value meets the desired value, so you must use the while loop to block. Note that when wait () is awakened, it only lets the while loop continue down. If if is used here, it means that if continues down and the if statement block will jump out. However, notifyAll is only responsible for waking up threads, and does not guarantee conditions, so it is necessary to manually guarantee the logic of the program.

Actual case

Next, we use wait () and notify () to implement a producer-consumer model. This is also a place that may be asked during the interview. As for what is the producer-consumer model, students who don't understand, please make your own Baidu.

The first is some basic code


private static Boolean run = true;// Control production and consumption 
private static final Integer MAX_CAPACITY = 5;// Maximum number of buffers 
private static final LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>();// Buffer queue 

Producer code


/**
 *  Producer 
 */
class Producter extends Thread {
  @Override
  public void run() {
    while (run) {
      synchronized (queue) {
        while (queue.size() >= MAX_CAPACITY * 2) {
          try {
            System.out.println(" Buffer queue is full, waiting for consumption ");
            queue.wait();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }

        try {
          String string = UUID.randomUUID().toString();
          queue.put(string);
          System.out.println(" Production :" + string);
          Thread.sleep(500);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }

        queue.notifyAll();// Notify producers and consumers 
      }
    }
  }
}

Consumer code


/**
 *  Consumer 
 */
class Consumer extends Thread {
  @Override
  public void run() {
    while (run) {
      synchronized (queue) {
        while (queue.isEmpty()) {
          try {
            System.out.println(" Queue empty, waiting for production ");
            queue.wait();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }

        try {
          System.out.println(" Consumption: " + queue.take());
          Thread.sleep(500);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }

        queue.notifyAll();// Notify producers and consumers 
      }
    }
  }
}

Code description

1. Both producers and consumers inherit the thread Thread, because wait and notify themselves are used for inter-thread communication

2. Both producers and consumers have two layers of while, and the outer layer of while is used to judge whether to run producers and consumers. The memory while is used to determine whether the queue queue is full or empty, and if the condition is met, the current thread becomes a waiting state (waiting notify).

3. The condition of inner layer judges why while is used instead of if, because when the thread is wait, the object lock will be released. When the waiting thread is notify, it must try to acquire the object lock again. If the object lock is not acquired, it must wait until the object lock is acquired before executing backwards.

4. When the producer produces one piece of data or the consumer consumes one piece of data, the notifyAll () method is used to notify all threads waiting for the lock of the current object, but only one waiting thread can get the lock at a time.

5. We use queue as the locked object to communicate between different threads

Code running result

Queue empty, waiting for production
Production: e422484e-8eb3-4a91-8a7c-97e21d7ef498
Production: 7894b802-2529-4798-ba98-9f0657f39240
Production: 848f6759-a427-4a94-89dc-3f484daaa467
Production: f711d3dc-972c-4c44-8640-faffe376d354
Production: 38a08e62-d774-4ed5-8b51-f1ad534c246f
Consumption: e422484e-8eb3-4a91-8a7c-97e21d7ef498
Consumption: 7894b802-2529-4798-ba98-9f0657f39240
Consumption: 848f6759-a427-4a94-89dc-3f484daaa467
Consumption: f711d3dc-972c-4c44-8640-faffe376d354
Consumption: 38a08e62-d774-4ed5-8b51-f1ad534c246f
Queue empty, waiting for production
Production: 9fae26f3-0b6e-4fbd-9620-040667efe0af
Production: 95bb1d88-e08a-4f70-a270-f75760994184
Consumption: 9fae26f3-0b6e-4fbd-9620-040667efe0af
Consumption: 95bb1d88-e08a-4f70-a270-f75760994184
Queue empty, waiting for production
Production: 13d304bc-dff3-44a4-9527-2e0facd884e7
Production: 2693e069-bae1-4beb-adcd-a3c3bf5d232b
Consumption: 13d304bc-dff3-44a4-9527-2e0facd884e7
Consumption: 2693e069-bae1-4beb-adcd-a3c3bf5d232b

It can also be seen from the results that production and consumption are irregular, because although notifyAll () notifies all waiting threads, it is not sure which thread can get the object lock. However, there is an obvious problem, because only one thread can get the object lock at the same time, and it is impossible for producers and consumers to run at the same time.


Related articles: