并发基础wait()方法的简介和使用

523 阅读4分钟

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()
     */
    public final void wait() throws InterruptedException {
        wait(0);
    }

wait()和notify()一系列的方法,是属于对象的,不是属于线程的。它们用在线程同步时,synchronized语句块中,wait会让出CPU而notify不会,notify重在于通知使用object的对象“我用完了!”,wait重在通知其它同用一个object的线程“我暂时不用了”并且让出CPU。

为什么要将wait()放在同步代码块中呢?

Lost Wake-Up Problem

首先我们来举个例子,一个消费者线程、一个生产者线程。生产者的任务为count+1,然后唤醒消费者;消费者的任务为count-1,等到count为0时陷入沉睡。

生产者伪代码:

count++;
notify();

消费者伪代码:

while(count <= 0){
    wait();
    count--;
}

熟悉多线程的朋友应该一眼就看出来了问题,如果生产者和消费者的步骤混杂在一起会发生什么。

首先我们先假设count = 0,这个时候消费者检查count的值,发现count <= 0的条件成立;就在这个时候,发生了上下文切换,生产者进来了,噼噼啪啪一顿操作,把两个步骤都执行完了,也就是发出了通知,准备唤醒一个线程。这个时候消费者刚决定睡觉,还没睡呢,所以这个通知就会被丢掉。紧接着,消费者就睡过去了……

img

这就是所谓的Lost Wake-Up Problem

如何解决

现在我们应该能发现问题的根源在于,消费者在检查count到调用wait()之间,count就可能被改掉了。 那我们如何解决呢? 让消费者和生产者竞争一把锁,竞争到了的,才能够修改count的值。

于是乎生产者代码:

lock();
count++;
notify();
unlock();

消费者代码:

lock();
while(count <= 0){
    wait();
    count--;
}
unlock();

现在我们来看看,这样子真的解决了吗?

答案是毫无卵用,依旧会出现lost wake up问题,而且和无锁的表现是一样的。

因为wait()是释放锁然后等待获取锁,当然要先获得锁才行,但在这边连锁都莫得。

终极答案

所以,我们可以总结到,为了避免出现这种 lost wake up 问题,在这种模型之下,总应该将我们的代码放进去的同步块中。

Java强制我们的 wait()/notify() 调用必须要在一个同步块中,就是不想让我们在不经意间出现这种 lost wake up 问题。

不仅仅是这两个方法,包括 java.util.concurrent.locks.Condition 的 await()/signal() 也必须要在同步块中。

正解:

private Object obj = new Object();
private Object anotherObj = new Object();

@Test
public void produce() {
    synchronized (obj) {
        try {
            //同步块要对当前线程负责,而不是anotherObj.notify();
            obj.notify();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

wait和notify的用法

wait()、notify()和notifyAll()

  • wait()、notify() 和 notifyAll()方法是本地方法,并且为 final 方法,无法被重写。
  • 调用某个对象的 wait() 方法能让当前线程阻塞,并且当前线程必须拥有此对象的 monitor(即锁,或者叫管程)。
  • 调用某个对象的 notify() 方法能够唤醒一个正在等待这个对象的 monitor 的线程,如果有多个线程都在等待这个对象的 monitor,则只能唤醒其中一个线程。
  • 调用 notifyAll() 方法能够唤醒所有正在等待这个对象的monitor的线程。

具体应用

/**
 * wait() && notify()方法
 * 这两个方法是在Object中定义的,用于协调线程同步,比 join 更加灵活
 */
public class NotifyDemo {
    public static void main(String[] args) {
        //写两个线程 1.图片下载
        Object obj=new Object();
        Thread download=new Thread(){
            public void run() {
                System.out.println("开始下载图片");
                for (int i = 0; i < 101; i+=10) {
                    System.out.println("down"+i+"%");
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("图片下载成功");
                synchronized (obj) {
                    obj.notify();//唤起
                }
                System.out.println("开始下载附件");
                for (int i = 0; i < 101; i+=10) {
                    System.out.println("附件下载"+i+"%");

                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
                System.out.println("附件下载成功");
            }
        };
        //2.图片展示
        Thread show=new Thread(){
            public void run(){
                synchronized (obj) {
                    try {
                        obj.wait();//阻塞当前
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("show:开始展示图片");
                    System.out.println("图片展示完毕");
                }

            }
        };
        download.start();
        show.start();
    }
}

img

作者:ZARD007 原文链接:juejin.cn/post/684490…

发布于 2020-03-13