Concurrency series "Understand wait()/notify()/notifyAll() through producer consumer scenarios"

wait(long timeout)/notify()/notifyAll()`

Many explanations on the Internet are either incomplete or biased, or just look at the official explanation

First look at the source code:

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

It can be seen that the three methods are all finalmodified local methods belonging to the Object class .

wait(long timeout)Explanation in API :

This method causes the current thread (call it <var>T</var>) to place itself in the wait set for this object and then to relinquish any and all synchronization claims on this object. Thread <var>T</var> becomes disabled for thread scheduling purposes and lies dormant until one of four things happens:
Some other thread invokes the {@code notify} method for this object and thread <var>T</var> happens to be arbitrarily chosen as the thread to be awakened.
Some other thread invokes the {@code notifyAll} method for this object.
Some other thread {@linkplain Thread#interrupt() interrupts} thread <var>T</var>.
The specified amount of real time has elapsed, more or less.  If {@code timeout} is zero, however, then real time is not taken into consideration and the thread simply waits until notified.

This method causes the current thread (calling it T ) to place itself in the waiting set of the object, and then abandon any and all synchronization statements on the object. For the purpose of thread scheduling, thread T is disabled and sleeps until one of the following four situations occurs:

  • Some other threads call the {@code notify} method for the object, and thread T happens to be arbitrarily selected as the thread to be awakened
  • Some other threads call the {@code notifyAll} method of the object
  • Other thread {@linkplain thread #interrupt() interrupt} thread T
  • The specified real-time time has passed, more or less. However, if {@code timeout} is 0, real-time is not considered, and the thread just waits until it receives a notification.

notify()Explanation in API :

Wakes up a single thread that is waiting on this object's monitor. If any threads are waiting on this object, one of them is chosen to be awakened. The choice is arbitrary and occurs at the discretion of the implementation. A thread waits on an object's monitor by calling one of the {@code wait} methods.
The awakened thread will not be able to proceed until the current thread relinquishes the lock on this object. The awakened thread will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened thread enjoys no reliable privilege or disadvantage in being the next thread to lock this object.

Wakes up a single thread waiting on the object monitor. If any threads are waiting for the object, one of them is selected to be awakened. The choice is arbitrary and is at the discretion of the implementation. The thread waits for the monitor of the object by calling a {@code wait} method.

The awakened thread will not be able to continue execution until the current thread gives up the lock on the object. The awakened thread will compete with other threads that may actively compete for synchronization on this object in the usual way; for example, the awakened thread has no reliable privileges or disadvantages in becoming the next thread to lock the object.

notify()Explanation in API :

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 {@code 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; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object.

Wake up all threads waiting on the object monitor. The thread waits for the monitor of the object by calling a {@code wait} method.

The awakened thread cannot continue to operate until the current thread gives up the lock on the object. The awakened thread will compete with other threads that may actively compete for synchronization on this object in the usual way; for example, the awakened thread has no reliable privileges or disadvantages when it becomes the next thread to lock the object.

wait(long timeout)/ notify()/ notifyAll()Use

wait()The current thread blocked, the premise is you must first obtain a lock, generally with synchronizedkeywords, ie, generally synchronizedusing a synchronized block in the wait()、notify/notifyAll()method.

Since wait() and notify/notifyAll() are executed in the synchronized code block, it means that the current thread must have acquired the lock

  • When the thread executes the wait() method, it will release the current lock, then give up the CPU and enter the waiting state;
  • Only when notify/notifyAll() is executed, one or more threads that are in the waiting state will be awakened, and then continue to execute until the code of the synchronized code block is executed or wait() is encountered in the middle, and then released again lock.

Here is an example in the producer and consumer scenario:

import java.util.LinkedList;
import java.util.Queue;

/**
 * @Author Hory
 * @Date 2020/10/19
 */
public class WaitTest {
    
    

    private static int maxsize = 2;
    private static Queue<Integer> queue = new LinkedList<>();

    public static void main(String[] args) {
    
    
        Thread producer = new Thread(new Runnable(){
    
    
            public void run(){
    
    
                while(true){
    
    
                    synchronized (queue) {
    
    
                        try {
    
    
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
    
    
                            e.printStackTrace();
                        }

                        System.out.println("producer: get queue lock");

                        while(queue.size() == maxsize){
    
    
                            System.out.println("queue is full, producer wait");
                            try {
    
    
                                queue.wait();
                            } catch (InterruptedException e) {
    
    
                                e.printStackTrace();
                            }
                        }
                        int num = (int) (Math.random()*100);
                        queue.offer(num);
                        System.out.println("producer: produce a element:" + num);
                        queue.notifyAll();
                        System.out.println("producer: exit a production process");
                    }
                }
            }
        });

        Thread consumer1 = new Thread(new Runnable(){
    
    
            public void run(){
    
    
                while(true){
    
    
                    synchronized (queue) {
    
    
                        try {
    
    
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
    
    
                            e.printStackTrace();
                        }
                        System.out.println("consumer1: get queue lock");

                        while(queue.isEmpty()){
    
    
                            System.out.println("queue is empty, consumer1 wait");
                            try {
    
    
                                queue.wait();
                            } catch (InterruptedException e) {
    
    
                                e.printStackTrace();
                            }
                        }
                        int num = queue.poll();
                        System.out.println("consumer1: consumer a element:" + num);
                        queue.notifyAll();
                        System.out.println("consumer1: exit a consumption process");
                    }
                }
            }
        });

        Thread consumer2 = new Thread(new Runnable(){
    
    
            public void run(){
    
    
                while(true){
    
    
                    synchronized (queue) {
    
    
                        try {
    
    
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
    
    
                            e.printStackTrace();
                        }
                        System.out.println("consumer2: get queue lock");

                        while(queue.isEmpty()){
    
    
                            System.out.println("queue is empty, consumer2 wait");
                            try {
    
    
                                queue.wait();
                            } catch (InterruptedException e) {
    
    
                                e.printStackTrace();
                            }
                        }
                        int num = queue.poll();
                        System.out.println("consumer2: consumer a element:" + num);
                        queue.notifyAll();
                        System.out.println("consumer2: exit a consumption process");
                    }
                }
            }
        });

        producer.start();
        consumer1.start();
        consumer2.start();
    }
}

Run as follows:

producer: get queue lock
producer: produce a element:40
producer: exit a production process

producer: get queue lock
producer: produce a element:84
producer: exit a production process

producer: get queue lock  
queue is full, producer wait  

consumer2: get queue lock
consumer2: consumer a element:40
consumer2: exit a consumption process

consumer2: get queue lock
consumer2: consumer a element:84
consumer2: exit a consumption process

consumer2: get queue lock
queue is empty, consumer2 wait

consumer1: get queue lock
queue is empty, consumer1 wait

producer: produce a element:66
producer: exit a production process

As above, a producer thread producerand two consumer threads are created consumer1、consumer2. The producer is responsible for the offer element in the queue, and the consumer is responsible for the poll element. When the producer obtains the lock, it will judge first. If the queue is full, execute wait () After entering the blocking queue and letting out the lock, if the queue is not full, it will not enter the while loop, and will not execute the wait() in the while (if wait() is not executed, the lock will not be let out). Always add the offer element to the queue, and call queue.notifyAll();it after each addition to wake up all waiting threads and re-participate in the lock competition.

The operation process of the consumer is the same as above.

Guess you like

Origin blog.csdn.net/weixin_44471490/article/details/109170523