JUC

多线程中的虚假唤醒


什么是虚假唤醒?

一般而言线程调用wait()方法后,需要其他线程调用notify,notifyAll方法后,线程才会从wait方法中返回, 而虚假唤醒(spurious wakeup)是指线程通过其他方式,从wait方法中返回。
 
假设有两个消费者线程,两个生产者线程。
先上错误代码,是用if的情况:
public class AirCondition {
    private int num=0;

    private synchronized void production() throws InterruptedException {
        //1.判断
        if(num>=1){
            this.wait();
        }
        //2.执行
        this.num++;
        System.out.println(Thread.currentThread().getName()+"生产1个此时资源量:"+num);
        //3.通知
        notifyAll();

    }
    private synchronized void consume() throws InterruptedException {
        if(num<=0){
            this.wait();
        }
        this.num--;
        System.out.println(Thread.currentThread().getName()+"消费1个此时资源量:"+num);
        notifyAll();
    }
    
    public static void main(String[] args) {
        AirCondition airCondition=new AirCondition();
        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    airCondition.consume();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        },"消费者1").start();

        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    airCondition.consume();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        },"消费者2").start();

        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    airCondition.production();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        },"生产者1").start();

        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    airCondition.production();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        },"生产者2").start();
    }

}
生产者1生产1个此时资源量:1
消费者2消费1个此时资源量:0
消费者1消费1个此时资源量:-1
生产者2生产1个此时资源量:0
生产者2生产1个此时资源量:1
消费者1消费1个此时资源量:0
消费者2消费1个此时资源量:-1
生产者1生产1个此时资源量:0
生产者1生产1个此时资源量:1
消费者2消费1个此时资源量:0
消费者1消费1个此时资源量:-1
生产者2生产1个此时资源量:0
生产者2生产1个此时资源量:1
消费者1消费1个此时资源量:0
消费者2消费1个此时资源量:-1
生产者1生产1个此时资源量:0
生产者1生产1个此时资源量:1
消费者2消费1个此时资源量:0
消费者1消费1个此时资源量:-1
生产者2生产1个此时资源量:0
生产者2生产1个此时资源量:1
消费者1消费1个此时资源量:0
消费者2消费1个此时资源量:-1
生产者1生产1个此时资源量:0
生产者1生产1个此时资源量:1
消费者2消费1个此时资源量:0
消费者1消费1个此时资源量:-1
生产者2生产1个此时资源量:0
生产者2生产1个此时资源量:1
消费者1消费1个此时资源量:0
消费者2消费1个此时资源量:-1
生产者1生产1个此时资源量:0
生产者1生产1个此时资源量:1
消费者2消费1个此时资源量:0
消费者1消费1个此时资源量:-1
生产者2生产1个此时资源量:0
生产者2生产1个此时资源量:1
消费者1消费1个此时资源量:0
消费者2消费1个此时资源量:-1
生产者1生产1个此时资源量:0

发现执行时资源量出现了-1的情况原因:

  1. 消费者1号线程抢到锁,进入同步代码块,发现没有资源,消费者1进入wait等待状态,外面三个线程竞争锁。
  2. 消费者2号线程抢到锁,进入同步代码块,发现没有资源,消费者2进入wait等待状态,外面两个线程竞争锁。(此时消费者1,2都处于等待状态)
  3. 生产者1号线程抢到锁,进入同步代码块,发现没有资源,执行生产资源,然后notifyAll。(此时消费者1,2都被唤醒)
  4. 消费者1从wait往后执行,不在进行if判断 消费了1个资源,资源剩余成为0。
  5. 消费者2从wait往后执行,不在进行if判断 消费了1个资源,资源剩余成为-1。(bug产生)
解决的办法是条件判断通过
while(条件){
  this.wait();
}
来解决
使用while后的结果:
生产者1生产1个此时资源量:1
消费者2消费1个此时资源量:0
生产者1生产1个此时资源量:1
消费者1消费1个此时资源量:0
生产者1生产1个此时资源量:1
消费者2消费1个此时资源量:0
生产者1生产1个此时资源量:1
消费者1消费1个此时资源量:0
生产者1生产1个此时资源量:1
消费者2消费1个此时资源量:0
生产者1生产1个此时资源量:1
消费者1消费1个此时资源量:0
生产者1生产1个此时资源量:1
消费者2消费1个此时资源量:0
生产者1生产1个此时资源量:1
消费者1消费1个此时资源量:0
生产者1生产1个此时资源量:1
消费者2消费1个此时资源量:0
生产者1生产1个此时资源量:1
消费者1消费1个此时资源量:0
生产者2生产1个此时资源量:1
消费者2消费1个此时资源量:0
生产者2生产1个此时资源量:1
消费者1消费1个此时资源量:0
生产者2生产1个此时资源量:1
消费者2消费1个此时资源量:0
生产者2生产1个此时资源量:1
消费者1消费1个此时资源量:0
生产者2生产1个此时资源量:1
消费者2消费1个此时资源量:0
生产者2生产1个此时资源量:1
消费者1消费1个此时资源量:0
生产者2生产1个此时资源量:1
消费者2消费1个此时资源量:0
生产者2生产1个此时资源量:1
消费者1消费1个此时资源量:0
生产者2生产1个此时资源量:1
消费者2消费1个此时资源量:0
生产者2生产1个此时资源量:1
消费者1消费1个此时资源量:0
此时能够正常运行了。
 
贴一下正确的代码:
public class AirCondition {
    private int num=0;

    private synchronized void production() throws InterruptedException {
        //1.判断
        while(num>=1){
            this.wait();
        }
        //2.执行
        this.num++;
        System.out.println(Thread.currentThread().getName()+"生产1个此时资源量:"+num);
        //3.通知
        notifyAll();

    }
    private synchronized void consume() throws InterruptedException {
        while(num<=0){
            this.wait();
        }
        this.num--;
        System.out.println(Thread.currentThread().getName()+"消费1个此时资源量:"+num);
        notifyAll();
    }

    public static void main(String[] args) {
        AirCondition airCondition=new AirCondition();
        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    airCondition.consume();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        },"消费者1").start();

        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    airCondition.consume();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        },"消费者2").start();

        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    airCondition.production();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        },"生产者1").start();

        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    airCondition.production();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        },"生产者2").start();
    }

}
​

使用while()判断的原因

while即使在条件成立的时候也还会再执行一次,而if判断条件成立了,就直接向下执行了
 
 
wait方法可以分为三个操作:
(1)释放锁并阻塞
(2)等待条件cond发生
(3)获取通知后,竞争获取锁
在多核处理器下,pthread_cond_signal可能会激活多于一个线程(阻塞在条件变量上的线程)。结果就是,当一个线程调用pthread_cond_signal()后,多个调用pthread_cond_wait()或pthread_cond_timedwait()的线程返回。这种效应就称为“虚假唤醒”。
  • 作者:低调做个路人 (扫码联系作者)
  • 发表时间:2019-11-26 14:00:00
  • 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
  • 评论