关于Object#wait() 的虚假唤醒

先看关于wait()方法的javadoc描述

Causes the current thread to wait until another thread invokes the notify() method or the 
notifyAll() method for this object. In other words, this method behaves exactly as if 
it simply performs the call wait(0).
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 notify method or the
 notifyAll method. The thread then waits until it can re-obtain
 ownership of the monitor and resumes execution.
As in the one argument version, interrupts and spurious wakeups 
are possible, and this method should always be used in a loop:

译文:
线程会等待,直到另一个线程执行这个对象的notify或notifyAll 方法,换句话说就是此方法相当于执行了wait(0)方法,
此方法执行是当前线程必须拥有此对象的监视器(否则会报IllegalMonitorStateException 异常),
当前线程会释放 此对象监视的所有权,
并等待直到另一个线程通过调用notify或notifyAll方法通知此等待对象监视器的线程唤醒,然后线程等待,
直到它重新获取到监视器所有权并继续执行。和单参数版本一样,
中断和虚假唤醒是可能的,并且该方法应始终在循环中使用。

而最后一句话便是本篇文章的重点:虚假唤醒。先看例子:

消费方法

 public synchronized void customer(){//消费
        if(product <= 0){
            System.out.println(Thread.currentThread().getName()+",产品缺货:");
            try {
                System.out.println("cus1111");
                this.wait();
                System.out.println("cus2222");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else{
            System.out.println(Thread.currentThread().getName()+",消费了一个产品:"+ product--);
            this.notifyAll();

        }
    }

生成方法

 public synchronized void product(){//生产
        if(product >= 1){
            System.out.println(Thread.currentThread().getName()+",产品满了:");
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else{
            System.out.println(Thread.currentThread().getName()+",生产了一个产品:"+ ++product);
            this.notifyAll();

        }
    }

消费次数和生成次数是一样的前提下,存在这样一种情况:

生成者还有两次执行权,消费者也有两次。假设此时产品数为0,生产者抢到了cpu执行权,进行了一次生产,然后生成者有抢到了cpu执行权,执行了一次,并等待在那了,之后生产者可以执行的次数为0了。

接着消费者得到了cpu执行权,进行了一次消费,并唤醒了等待中的生产者。之后假设消费者抢到了cpu执行权,就先进行消费,此时produce为0,消费者等待在那了,此时生产者得到了cpu执行权,从wait方法往下执行,不会执行notifyAll方法,等待中的消费者无法被唤醒,然后生产者线程就退出了,无人再来唤醒消费者线程了。

结果如下,程序无法正常退出

c2d556aaa8913cc2505aca06efb284630da.jpg

解决办法:将else去掉。这样就能在生产者被唤醒后,执行notifyAll方法,从而唤醒等待中的消费者。但这样是存在问题的,什么问题呢?那就是本篇文章的主角:虚假唤醒。

我们先在生产者线程谁200毫秒,模拟业务操作,并开启两个生产者,两个消费者,同时对Clerk 的产品 进行操作,主要代码如下;

  Clerk clerk=new Clerk();
        new Thread(new Prod(clerk), "prod-1").start();
        new Thread(new Customer(clerk),"cus-1").start();
        new Thread(new Prod(clerk), "prod-2").start();
        new Thread(new Customer(clerk),"cus-2").start();



---------------------------------------------------------------

  @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.prod();
        }
    }

结果:程序能正常退出,但却产生了负数产品

2dfb42477f76b7b9500049489ce0bac98e8.jpg

原因:一旦两个消费者同时进入了wait方法,同时释放了cpu执行权,然后由生产者进行notifyAll 进行唤醒,两个消费者线程同时对produce 进行消费,所以才产生了负数产品。

解决:根据wait的javadoc建议,将wait() 放在循环里进行操作即可,这样当两个消费者同时被唤醒时,就会再判定一次,从而不会产生虚假唤醒问题。

最后附上完整的代码

public class WatiTest {

    public static void main(String[] args) {
        Clerk clerk=new Clerk();
        new Thread(new Prod(clerk), "prod-1").start();
        new Thread(new Customer(clerk),"cus-1").start();
        new Thread(new Prod(clerk), "prod-2").start();
        new Thread(new Customer(clerk),"cus-2").start();
    }

}

class Clerk{
    int produce=0;
    public synchronized void prod(){
        if(produce >= 1){
            System.out.println(Thread.currentThread().getName()+",满了");
            try {
                this.wait();
                System.out.println(Thread.currentThread().getName()+",被唤醒");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName()+",生产了一个产品:"+ ++produce);
            this.notifyAll();
    }
    public synchronized void sale(){
        if(produce <= 0){
            System.out.println(Thread.currentThread().getName()+",缺货");
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
            System.out.println(Thread.currentThread().getName()+",消费了一个产品:"+ produce--);
            this.notifyAll();
    }
}
class Prod implements Runnable{

    Clerk clerk;

    public Prod(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.prod();
        }
    }
}
class Customer implements Runnable{

    Clerk clerk;

    public Customer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            clerk.sale();
        }
    }
}

 

转载于:https://my.oschina.net/u/3574106/blog/3014760

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值