• Java虚假唤醒及如何避免虚假唤醒


    Java虚假唤醒及如何避免虚假唤醒

    先给出一段虚假唤醒的代码如下:

    package bat.ke.qq.com.learnjuc.thread;
    
    public class SpuriousWakeup {
    
        public static void main(String[] args) {
            Data2 data = new Data2();
    
            new Thread(() -> {
                for (int i = 0; i < 5; i++) {
                    try {
                        data.increment();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "生产者A").start();
    
            new Thread(() -> {
                for (int i = 0; i < 5; i++) {
                    try {
                        data.increment();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "生产者B").start();
    
            new Thread(() -> {
                for (int i = 0; i < 5; i++) {
                    try {
                        data.decrement();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "消费者C").start();
            new Thread(() -> {
                for (int i = 0; i < 5; i++) {
                    try {
                        data.decrement();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "消费者D").start();
        }
    }
    
    //线程操作的资源类,判断等待业务、通知
    class Data2 {
        private int number = 0;
    
        //+1
        public synchronized void increment() throws InterruptedException {
            if (number != 0) {
                //等待
                this.wait();
            }
            number++;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            //通知其他线程,我+1完毕了
            this.notifyAll();
        }
    
        //-1
        public synchronized void decrement() throws InterruptedException {
            if (number == 0) {
                this.wait();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            //通知其他线程,我-1完毕了
            this.notifyAll();
        }
    
    
    }
    
    
    
    //执行以上的代码结果输出如下:
    生产者A=>1 
    消费者C=>0 
    生产者B=>1 
    生产者A=>2 
    生产者B=>3
    消费者C=>2
    消费者C=>1
    消费者C=>0
    生产者B=>1
    生产者A=>2
    生产者B=>3
    消费者C=>2
    生产者B=>3
    生产者A=>4
    消费者D=>3
    消费者D=>2
    消费者D=>1
    消费者D=>0
    生产者A=>1
    消费者D=>0
    
    
    

    1、num=0 ,生产者A 拿到对象锁进入同步块中执行num++,num = 1,打印:生产者A=>1,

    2、num=1,生产者A未释放对象锁又进入同步块中,但是此时num!=0,生产者A waiting在num=0 上,释放锁。 生产者A此时的状态为waiting状态,等待消费者唤醒它能够进入阻塞状态

    3.num=1 ,生产者B拿到对象锁进入同步代码块中,但是此时num!=0,生产者B waiting在num=0 上,释放锁。 生产者B此时的状态为waiting状态,等待消费者唤醒它能够进入阻塞状态

    a、b   waiting 。。。。。。。。。。。

    4.num=1,消费者C拿到对象锁进入同步代码块中,执行num-- ,打印: 消费者C=>0 。

    然后notifyall 生产者A、生产者B 。

    生产者A、生产者B被消费者C唤醒,他们的的状态从waiting状态更改成block状态 (a、b waiting ==> a、b  blocking)。 在被唤醒的那一瞬间,几毫秒中, 生产者A、生产者B是竞争关系, 都在等待消费者C 释放锁,并且争夺对象锁。

    5.消费者C重新进入代码块中,因为num=0 ,所以消费者c waiting了 ,(c running -》c waiting)

    消费者C 释放锁,生产者B 抢到 对象锁 。。(b blocking -> b running, a blocking 保持不变,) 仔细看 生产者的同步块,

    public synchronized void increment() throws InterruptedException {
    if (number != 0) {
    //等待
    this.wait();
    }
    number++;
    System.out.println(Thread.currentThread().getName() + "=>" + number);
    //通知其他线程,我+1完毕了
    this.notifyAll();
    }

    上述代码使用if判断,唤醒后线程会从wait之后的代码开始运行,但是不会重新判断if条件,直接继续运行if代码块之后的代码

    生产者B 直接执行num++ 操作 ,打印:生产者B=>1, , 生产者B也唤醒了消费者c ,消费者从waiting变成blocing

    c waiting -> c blocking

    6.生产者B重新 进入同步块中,waiting在num!=0 上 ,生产者B释放锁

    (b running-> b  waiting)

    7.生产者A抢到 对象锁 。。(a blocking -> a running) 仔细看 生产者的同步块,

    public synchronized void increment() throws InterruptedException {
    if (number != 0) {
    //等待
    this.wait();
    }
    number++;
    System.out.println(Thread.currentThread().getName() + "=>" + number);
    //通知其他线程,我+1完毕了
    this.notifyAll();
    }

    上述代码使用if判断,唤醒后线程会从wait之后的代码开始运行,但是不会重新判断if条件,直接继续运行if代码块之后的代码

    生产者A 直接执行num++ 操作 ,打印:生产者A=>2, 注意: 此时生产者A 执行notify all ,生产者B被 唤醒了 。。。

    (b waiting->blocking)

    紧接着生产者A 重新进入同步块,此时num =2 ,生产者A的状态变为waiting(a running -> a blocking)

    8.生产者B和消费者C 都是竞争对象锁,生产者B 很争气 又抢到了锁。 (b blocking->b running) 于是打印:生产者B=>3

    9.接下来: b runing -》b waiting , c blocing -> c running ,所以呢打印:

    消费者C=>2    消费者C=>1   消费者C=>0

    1. a、b waiting-》 a、b blocing ,

      选择a、b中的一个running。。。。。。。。。。。。。。。。。。。。。。

    什么叫虚假唤醒?
    站在上述两个消费者线程的角度上讲, 无论哪一个线程抢到了资源, 另一个线程的唤醒就可以被认为是没有必要的, 也就是被虚假唤醒了。

    虚假唤醒发生的场景以及解决方式?
    上述代码使用if判断,唤醒后线程会从wait之后的代码开始运行,但是不会重新判断if条件,直接继续运行if代码块之后的代码,而如果使用while的话,也会从wait之后的代码运行,但是唤醒后会重新判断循环条件,如果不成立再执行while代码块之后的代码块,成立的话继续wait。

    解决虚假唤醒生产者消费者实例代码:

    package bat.ke.qq.com.learnjuc.thread;
    
    public class SpuriousWakeup {
    
        public static void main(String[] args) {
            Data2 data = new Data2();
    
            new Thread(() -> {
                for (int i = 0; i < 5; i++) {
                    try {
                        data.increment();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "生产者A").start();
    
            new Thread(() -> {
                for (int i = 0; i < 5; i++) {
                    try {
                        data.increment();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "生产者B").start();
    
            new Thread(() -> {
                for (int i = 0; i < 5; i++) {
                    try {
                        data.decrement();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "消费者C").start();
            new Thread(() -> {
                for (int i = 0; i < 5; i++) {
                    try {
                        data.decrement();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "消费者D").start();
        }
    }
    
    //线程操作的资源类,判断等待业务、通知
    class Data2 {
        private int number = 0;
    
        //+1
        public synchronized void increment() throws InterruptedException {
            while (number != 0) {//0
                //等待
                this.wait();
            }
            number++;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            //通知其他线程,我+1完毕了
            this.notifyAll();
        }
    
        //-1
        public synchronized void decrement() throws InterruptedException {
            while (number == 0) {
                this.wait();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            //通知其他线程,我-1完毕了
            this.notifyAll();
        }
    
    
    }
    
    //执行以上的代码结果输出如下:
    生产者A=>1
    消费者C=>0
    生产者B=>1
    消费者C=>0
    生产者A=>1
    消费者C=>0
    生产者B=>1
    消费者C=>0
    生产者A=>1
    消费者C=>0
    生产者B=>1
    消费者D=>0
    生产者A=>1
    消费者D=>0
    生产者B=>1
    消费者D=>0
    生产者A=>1
    消费者D=>0
    生产者B=>1
    消费者D=>0
    

    1、num=0 ,生产者A 拿到对象锁进入同步块中执行num++,num = 1,打印:生产者A=>1,

    2、num=1,生产者A未释放对象锁又进入同步块中,但是此时num!=0,生产者A waiting在num=0 上,释放锁。 生产者A此时的状态为waiting状态,等待消费者唤醒它能够进入阻塞状态

    3.num=1 ,生产者B拿到对象锁进入同步代码块中,但是此时num!=0,生产者B waiting在num=0 上,释放锁。 生产者B此时的状态为waiting状态,等待消费者唤醒它能够进入阻塞状态

    a、b   waiting 。。。。。。。。。。。

    4.num=1,消费者C拿到对象锁进入同步代码块中,执行num-- ,打印: 消费者C=>0 。

    然后notifyall 生产者A、生产者B 。

    生产者A、生产者B被消费者C唤醒,他们的的状态从waiting状态更改成block状态 (a、b waiting ==> a、b  blocking)。 在被唤醒的那一瞬间,几毫秒中, 生产者A、生产者B是竞争关系, 都在等待消费者C 释放锁,并且争夺对象锁。

    5.消费者c重新进入代码块中,因为num=0 ,所以消费者c waiting了 (c running -》c waiting)

    6、消费者C 释放锁,生产者B 抢到 对象锁 。。(b blocking -> b running, a blocking 保持不变,) 仔细看 生产者的同步块,

    public synchronized void increment() throws InterruptedException {
    while(number != 0) {
    //等待
    this.wait();
    }
    number++;
    System.out.println(Thread.currentThread().getName() + "=>" + number);
    //通知其他线程,我+1完毕了
    this.notifyAll();
    }

    使用while的话,也会从wait之后的代码运行,但是唤醒后会重新判断循环条件,如果不成立再执行while代码块之后的代码块,成立的话继续wait。

    打印:生产者B=>1,font> , 同时 生产者B也唤醒了消费者c ,消费者C从waiting变成blocing

    c waiting -> c blocking

    1. 生产者B重新 进入同步块中,waiting在num!=0 上 ,生产者B释放锁

    (b running-> b  waiting)

    8.生产者A抢到 对象锁 。。(a blocking -> a running) 仔细看 生产者的同步块, 因为是while判断,生产者A 判断是否要睡觉,并且睡觉

    生产者A和生产者B同样等在了num != 0 ,生产者A的状态变为waiting(a running -> a waiting)

    此时 a、b 都为waiting 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

    只有消费者c 是blocing 可以在cpu上面执行。。。。

    这边的重点是,c 唤醒 a、b 【a、b waiting ==> a、b blocking ]

    a和b 就算都是blocking,但是也只有一个能进到临界区对num进行操作,

    比如a进入临界区之后a waiting了,b进入临界区后,判断睡觉条件,并且睡觉。。。b 也waiting了,

  • 相关阅读:
    Android开始之ListView1
    Android开始之 Scrollview
    Android开始之 ProgressBar/seekBar/评分控件
    Android开始之异步图片下载
    Android开始之圆形/方形进度条
    Android开始之 普通/自定义Toast
    Android开始之dialog警告框,单选/多选框/自定义对话框
    Android开始之 activity_lifecycle和现场保护
    Android开始之 activity值回传
    [tool]VMware Workstation
  • 原文地址:https://www.cnblogs.com/tangliMeiMei/p/15327859.html
Copyright © 2020-2023  润新知