wait和notify详解


概述

java中多线程常用到wait和notify等方法,但是对此缺乏一个清晰的认识,本文主要介绍JAVA中wait和notify以及notifyAll的使用.

Wait

    /**
         * Causes the current thread to wait until another thread invokes the
         * {@link java.lang.Object#notify()} method or the
         * {@link java.lang.Object#notifyAll()} method for this object.
         * In other words, this method behaves exactly as if it simply
         * performs the call {@code wait(0)}.
         * <p>
         * The current thread must own this object's monitor. The thread
         * releases ownership of this monitor and waits until another thread
         * notifies threads waiting on this object's monitor to wake up
         * either through a call to the {@code notify} method or the
         * {@code notifyAll} method. The thread then waits until it can
         * re-obtain ownership of the monitor and resumes execution.
         * <p>
         * As in the one argument version, interrupts and spurious wakeups are
         * possible, and this method should always be used in a loop:
         * <pre>
         *     synchronized (obj) {
         *         while (&lt;condition does not hold&gt;)
         *             obj.wait();
         *         ... // Perform action appropriate to condition
         *     }
         * </pre>
         * This method should only be called by a thread that is the owner
         * of this object's monitor. See the {@code notify} method for a
         * description of the ways in which a thread can become the owner of
         * a monitor.
         *
         * @throws  IllegalMonitorStateException  if the current thread is not
         *               the owner of the object's monitor.
         * @throws  InterruptedException if any thread interrupted the
         *             current thread before or while the current thread
         *             was waiting for a notification.  The <i>interrupted
         *             status</i> of the current thread is cleared when
         *             this exception is thrown.
         * @see        java.lang.Object#notify()
         * @see        java.lang.Object#notifyAll()
         */
> wait方法调用后会使持有该对象的线程把对象的控制权交出去,也就是释放锁,然后原来的线程处于等待状态.  
> wait和notify是用来做线程通信的,wait调用后必须是另一个线程中调用notify或者notifyAll来唤醒线程.  
> 并且wait方法只能在拥有前对象的镜像中调用,也就是在synchronized同步代码块中调用,否则会报IllegalMonitorStateException异常.

Notify

    /**
         * 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.
         * <p>
         * 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.
         * <p>
         * This method should only be called by a thread that is the owner
         * of this object's monitor. A thread becomes the owner of the
         * object's monitor in one of three ways:
         * <ul>
         * <li>By executing a synchronized instance method of that object.
         * <li>By executing the body of a {@code synchronized} statement
         *     that synchronizes on the object.
         * <li>For objects of type {@code Class,} by executing a
         *     synchronized static method of that class.
         * </ul>
         * <p>
         * Only one thread at a time can own an object's monitor.
         *
         * @throws  IllegalMonitorStateException  if the current thread is not
         *               the owner of this object's monitor.
         * @see        java.lang.Object#notifyAll()
         * @see        java.lang.Object#wait()
         */
> 通知某个正在等待这个对象的控制权的线程可以继续运行,注意这里唤醒的是一个单一线程使该线程重新获取这个对象的锁,获取锁并不代表就可以马上执行.  
> 新唤醒的线程不会马上执行,而是和其他线程竞争执行权限,除非该线程设置了特有的线程优先级.  
> 同样的必须在synchronized同步代码块中调用,否则会报IllegalMonitorStateException异常.

NotifyAll

    /**
         * 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.
         * <p>
         * 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.
         * <p>
         * This method should only be called by a thread that is the owner
         * of this object's monitor. See the {@code notify} method for a
         * description of the ways in which a thread can become the owner of
         * a monitor.
         *
         * @throws  IllegalMonitorStateException  if the current thread is not
         *               the owner of this object's monitor.
         * @see        java.lang.Object#notify()
         * @see        java.lang.Object#wait()
         */
    
> 唤醒等待获取此对象控制权限的所有线程,同样的这些线程不会马上执行,而是去CPU哪里竞争执行权限.  
> 也必须在synchronized同步代码块中执行,,否则会报IllegalMonitorStateException异常.

使用-例子-1

    package com.sefon.gateway;
    
    /**
    * @Description:    
    * @author: zhouqiang
    * @version: 1.0, Apr 4, 2019
    */
    
    public class OutputThread implements Runnable {
    
        private int num;
        private Object lock;
    
        public OutputThread(int num, Object lock) {
            super();
            this.num = num;
            this.lock = lock;
        }
    
        public void run() {
            try {
                while(true){
                    synchronized(lock){
                          lock.notifyAll();//如果此处先wait,在notify不会打印任何结果
                        lock.wait();
                        Thread.sleep(1000);
                        System.out.println(num);
                    }
                }
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    
        }
    
        public static void main(String[] args){
            final Object lock = new Object();
    
            Thread thread1 = new Thread(new OutputThread(1, lock));
            Thread thread2 = new Thread(new OutputThread(2, lock));
    
            thread1.start();
            thread2.start();
        }
    
    }

这是一个轮询打印数字的多线程演示,为何lock先wait再notifyAll不会打印任何东西,而必须先notify才行呢?

> 分析:如果先wait,那线程1 2先后启动,然后都wait了那这样线程1 2都会等待另一个线程来notify。  
> 但是显然这个线程3是不存在的,这样就没人来唤醒这两个等待中的线程,所以wait之后的代码便不会再执行了.

使用-例子-2

再来看下面一个简单的例子

WaitThread只负责wait然后执行业务代码

    package com.sefon.gateway;
    
    /**
    * @Description:    
    * @author: jackromer
    * @version: 1.0, Apr 4, 2019
    */
    
    public class WaitThread implements Runnable {
    
    
        private int num;
        private Object lock;
    
        public WaitThread(int num, Object lock) {
            super();
            this.num = num;
            this.lock = lock;
        }
    
        public void run() {
            try {
                synchronized(lock){
                    System.out.println(Thread.currentThread().getName() + " :wait for notify");
                    lock.wait();//wait调用后, wait之后的代码就暂时不会再执行了, 必须等到其他线程notifyAll, 直到当前线程重新在CPU哪里竞争到了object的锁,当前线程的后序代码才会执行 .
                    System.out.println(Thread.currentThread().getName() + " :i have bean waked up and i get the lock from cpu, i am free ,i will continue to process..., my num is :" + num);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
        }
    
        public static void main(String[] args) throws InterruptedException {
            final Object lock = new Object();
            Thread waitThread1     = new Thread(new WaitThread(1, lock));
            Thread waitThread2     = new Thread(new WaitThread(2, lock));
            Thread waitThread3     = new Thread(new WaitThread(3, lock));
            Thread notifyThread = new Thread(new NotifyThread(lock));//notifyThread 用来唤醒之前wait的所有线程
    
            waitThread1.start();
            waitThread2.start();
            waitThread3.start();
            Thread.sleep(2000);//此处sleep是为了让wait的三个线程先启动,后面再启动notify线程。
            notifyThread.start();
        }
    
    }

NotifyThread只负责唤醒wait object的所有线程

    package com.sefon.gateway;
    
    /**
    * @Description:    
    * @author: jackromer
    * @version: 1.0, Apr 4, 2019
    */
    
    public class NotifyThread implements Runnable {
    
        private Object lock;
        public NotifyThread(Object lock) {
            super();
            this.lock = lock;
        }
    
        public void run() {//用于notify 所有等待此对象的线程
            synchronized(lock){
                lock.notifyAll();//注意,此处notifyAll虽然唤醒了所有等待object的线程,但是不一定按之前线程的先后顺序继续执行,而是谁先去CPU哪里竞争到锁谁就先执行
                System.out.println("I am notify thread ,i will wake up all threads which are waiting for this object.");
            }
        }
    
    }

启动main方法运行后的结果:

    //分析:线程执行顺序分别为0 2 1,这三个线程都会等待notify
    Thread-0 :wait for notify 
    Thread-2 :wait for notify
    Thread-1 :wait for notify
    //notifyAll线程唤醒了线程1 2 3,1 2 3线程重新去CPU哪里获取执行权限,明显的他们抢到执行权限的顺序为2 3 1
    I am notify thread ,i will wake up all threads which are waiting for this object.
    Thread-1 :i have bean waked up and i get the lock from cpu, i am free ,i will continue to process..., my num is :2
    Thread-2 :i have bean waked up and i get the lock from cpu, i am free ,i will continue to process..., my num is :3
    Thread-0 :i have bean waked up and i get the lock from cpu, i am free ,i will continue to process..., my num is :1

线程的生命周期

thread-life.png

总结

> wait notify和notifyAll如果光看注释,可能不那么容易理解,最关键的还是试验一下,会有不一样的收获。
但只要记住一点这三个是用来做线程通信的,必须在同步代码块中执行,
他们都是object顶层对象的方法,所以你应该清楚这三个和线程没关系而是和对象的锁有关系,
而线程只是用到了锁而已关系结构可以理解为这样:  
> OBJECT(只有一个) — LOCK(只有一个) — THREADS(线程可以有很多个,但都必须用同一个对象的同一个锁)。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容