Java concurrent programming: two ways of cooperation between threads: wait, notify, notifyAll and Condition

The original link Java concurrent programming: two ways of cooperation between threads: wait, notify, notifyAll and Condition

Earlier we talked a lot about synchronization, but in reality, cooperation between threads is required. For example, the most classic producer-consumer model: when the queue is full, the producer needs to wait for the queue to have space to continue putting goods into it, and during the waiting period, the producer must release the critical resources (ie the queue) occupancy rights. Because if the producer does not release the right to occupy the critical resource, the consumer will not be able to consume the goods in the queue, and there will be no space in the queue, so the producer will wait indefinitely. Therefore, in general, when the queue is full, the producer will hand over the right to occupy the critical resource and enter the suspended state. Then wait for the consumer to consume the item, and then the consumer notifies the producer that the queue has space. Likewise, when the queue is empty, the consumer must wait, waiting for the producer to notify it that there is an item in the queue. This process of communicating with each other is the cooperation between threads.

Today we will discuss the two most common ways of thread cooperation in Java: using Object.wait(), Object.notify() and using Condition.

wait()、notify()和notifyAll()

wait(), notify() and notifyAll() are methods in the Object class:

/**
 * 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 wait methods
 */
public final native void notify();
 
/**
 * 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.
 */
public final native void notifyAll();
 
/**
 * Causes the current thread to wait until either another thread invokes the
 * {@link java.lang.Object#notify()} method or the
 * {@link java.lang.Object#notifyAll()} method for this object, or a
 * specified amount of time has elapsed.
 * <p>
 * The current thread must own this object's monitor.
 */
public final native void wait(long timeout) throws InterruptedException;

From the textual descriptions of these three methods, the following information can be known:

  1. The wait(), notify() and notifyAll() methods are local and final and cannot be overridden.

  2. Calling the wait() method of an object can block the current thread, and the current thread must own the monitor (ie lock) of this object

  3. Calling the notify() method of an object can wake up a thread that is waiting for the monitor of this object. If there are multiple threads waiting for the monitor of this object, only one thread can be woken up;

  4. Call the notifyAll() method to wake up all monitor threads waiting for this object;

  Some friends may have questions: why are these three methods not declared in the Thread class, but the methods declared in the Object class (of course, since the Thread class inherits the Object class, Thread can also call the three methods)? In fact, the problem is very simple. Since each object has a monitor (that is, a lock), making the current thread wait for the lock of an object, of course, should be operated through this object. Instead of using the current thread to operate, because the current thread may wait for the lock of multiple threads, it is very complicated to operate through threads.

  As mentioned above, if the wait() method of an object is called, the current thread must own the monitor (that is, the lock) of the object, so calling the wait() method must be done in a synchronized block or synchronized method (synchronized block or synchronized method). ).

  Calling the wait() method of an object is equivalent to asking the current thread to hand over the monitor of the object, then enter the waiting state, and wait for the lock of the object to be acquired again later (the sleep method in the Thread class makes the current thread suspend execution for a period of time, Thus giving other threads a chance to continue execution, but it does not release the object lock);

  The notify() method can wake up a thread that is waiting for the monitor of the object. When multiple threads are waiting for the monitor of the object, only one of the threads can be woken up, and it is unknown which thread to wake up.

  Similarly, when calling the notify() method of an object, the current thread must also own the monitor of the object, so calling the notify() method must be done in a synchronized block or synchronized method (synchronized block or synchronized method).

  The nofityAll() method can wake up all the threads waiting for the object's monitor, which is different from the notify() method.

  One thing to note here: the notify() and notifyAll() methods just wake up the thread waiting for the monitor of the object, and do not decide which thread can get the monitor.

  To give a simple example: if there are three threads Thread1, Thread2 and Thread3 are waiting for the monitor of the object objectA, at this time Thread4 has the monitor of the object objectA, when the objectA.notify() method is called in Thread4, Thread1, Thread2 and Thread3 Only one can be awakened. Note that being awakened does not mean that the monitor of objectA is immediately obtained. If the objectA.notifyAll() method is called in Thread4, the three threads of Thread1, Thread2 and Thread3 will be awakened. As for which thread can obtain the monitor of objectA next, it depends on the scheduling of the operating system.

  In particular, it should be noted that a thread is woken up does not mean that the monitor of the object is immediately acquired. Only after calling notify() or notifyAll() and exiting the synchronized block, the rest of the threads can obtain the lock for execution after releasing the object lock.

Let's see an example to understand:

public class Test {
    public static Object object = new Object();
    public static void main(String[] args) {
        Thread1 thread1 = new Thread1();
        Thread2 thread2 = new Thread2();
         
        thread1.start();
         
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
         
        thread2.start();
    }
     
    static class Thread1 extends Thread{
        @Override
        public void run() {
            synchronized (object) {
                try {
                    object.wait();
                } catch (InterruptedException e) {
                }
                System.out.println("线程"+Thread.currentThread().getName()+"获取到了锁");
            }
        }
    }
     
    static class Thread2 extends Thread{
        @Override
        public void run() {
            synchronized (object) {
                object.notify();
                System.out.println("线程"+Thread.currentThread().getName()+"调用了object.notify()");
            }
            System.out.println("线程"+Thread.currentThread().getName()+"释放了锁");
        }
    }
}

No matter how many times it is run, the result of the run must be:

线程Thread-1调用了object.notify()
线程Thread-1释放了锁
线程Thread-0获取到了锁

Condition

Condition only appeared in java 1.5. It is used to replace the traditional Object's wait() and notify() to achieve collaboration between threads. Compared with using Object's wait() and notify(), use Condition1's await() , signal() This way to achieve inter-thread collaboration is safer and more efficient. Therefore, it is generally recommended to use Condition, which is described in the blog post of blocking queue. Blocking queue actually uses Condition to simulate cooperation between threads.

  • Condition is an interface, and the basic methods are await() and signal() methods;
  • Condition depends on the Lock interface, and the basic code to generate a Condition is lock.newCondition();
  • Calling the await() and signal() methods of Condition must be protected by lock, that is to say, it must be used between lock.lock() and lock.unlock.

await() in Conditon corresponds to Object's wait();

signal() in Condition corresponds to notify() of Object;

signalAll() in Condition corresponds to notifyAll() of Object.
  

Implementation of the Producer-Consumer Model

Implemented using Object's wait() and notify()

public class Test {
    private int queueSize = 10;
    private PriorityQueue<Integer> queue = new PriorityQueue<Integer>(queueSize);
      
    public static void main(String[] args)  {
        Test test = new Test();
        Producer producer = test.new Producer();
        Consumer consumer = test.new Consumer();
          
        producer.start();
        consumer.start();
    }
      
    class Consumer extends Thread{
          
        @Override
        public void run() {
            consume();
        }
          
        private void consume() {
            while(true){
                synchronized (queue) {
                    while(queue.size() == 0){
                        try {
                            System.out.println("队列空,等待数据");
                            queue.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                            queue.notify();
                        }
                    }
                    queue.poll();          //每次移走队首元素
                    queue.notify();
                    System.out.println("从队列取走一个元素,队列剩余"+queue.size()+"个元素");
                }
            }
        }
    }
      
    class Producer extends Thread{
          
        @Override
        public void run() {
            produce();
        }
          
        private void produce() {
            while(true){
                synchronized (queue) {
                    while(queue.size() == queueSize){
                        try {
                            System.out.println("队列满,等待有空余空间");
                            queue.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                            queue.notify();
                        }
                    }
                    queue.offer(1);        //每次插入一个元素
                    queue.notify();
                    System.out.println("向队列取中插入一个元素,队列剩余空间:"+(queueSize-queue.size()));
                }
            }
        }
    }
}

Implemented using Condition

public class Test {
    private int queueSize = 10;
    private PriorityQueue<Integer> queue = new PriorityQueue<Integer>(queueSize);
    private Lock lock = new ReentrantLock();
    private Condition notFull = lock.newCondition();
    private Condition notEmpty = lock.newCondition();
     
    public static void main(String[] args)  {
        Test test = new Test();
        Producer producer = test.new Producer();
        Consumer consumer = test.new Consumer();
          
        producer.start();
        consumer.start();
    }
      
    class Consumer extends Thread{
          
        @Override
        public void run() {
            consume();
        }
          
        private void consume() {
            while(true){
                lock.lock();
                try {
                    while(queue.size() == 0){
                        try {
                            System.out.println("队列空,等待数据");
                            notEmpty.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    queue.poll();                //每次移走队首元素
                    notFull.signal();
                    System.out.println("从队列取走一个元素,队列剩余"+queue.size()+"个元素");
                } finally{
                    lock.unlock();
                }
            }
        }
    }
      
    class Producer extends Thread{
          
        @Override
        public void run() {
            produce();
        }
          
        private void produce() {
            while(true){
                lock.lock();
                try {
                    while(queue.size() == queueSize){
                        try {
                            System.out.println("队列满,等待有空余空间");
                            notFull.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    queue.offer(1);        //每次插入一个元素
                    notEmpty.signal();
                    System.out.println("向队列取中插入一个元素,队列剩余空间:"+(queueSize-queue.size()));
                } finally{
                    lock.unlock();
                }
            }
        }
    }
}

References

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325199298&siteId=291194637