美文网首页
Java多线程中的虚假唤醒和如何避免

Java多线程中的虚假唤醒和如何避免

作者: 随机的未知 | 来源:发表于2020-12-03 18:48 被阅读0次

    先来看一个例子

    一个卖面的面馆,有一个做面的厨师和一个吃面的食客,需要保证,厨师做一碗面,食客吃一碗面,不能一次性多做几碗面,更不能没有面的时候吃面;按照上述操作,进行十轮做面吃面的操作。

    用代码说话

    首先我们需要有一个资源类,里面包含面的数量,做面操作,吃面操作;
    当面的数量为0时,厨师才做面,做完面,需要唤醒等待的食客,否则厨师需要等待食客吃完面才能做面;
    当面的数量不为0时,食客才能吃面,吃完面需要唤醒正在等待的厨师,否则食客需要等待厨师做完面才能吃面;
    然后在主类中,我们创建一个厨师线程进行10次做面,一个食客线程进行10次吃面;
    代码如下:

    package com.duoxiancheng.code;
    
    /**
     * @user: code随笔
     */
    
    class Noodles{
    
        //面的数量
        private int num = 0;
    
        //做面方法
        public synchronized void makeNoodles() throws InterruptedException {
            //如果面的数量不为0,则等待食客吃完面再做面
            if(num != 0){
                this.wait();
            }
    
            num++;
            System.out.println(Thread.currentThread().getName()+"做好了一份面,当前有"+num+"份面");
            //面做好后,唤醒食客来吃
            this.notifyAll();
        }
    
        //吃面方法
        public synchronized void eatNoodles() throws InterruptedException {
            //如果面的数量为0,则等待厨师做完面再吃面
            if(num == 0){
                this.wait();
            }
    
            num--;
            System.out.println(Thread.currentThread().getName()+"吃了一份面,当前有"+num+"份面");
            //吃完则唤醒厨师来做面
            this.notifyAll();
        }
    
    }
    
    public class Test {
    
        public static void main(String[] args) {
    
            Noodles noodles = new Noodles();
    
            new Thread(new Runnable(){
                @Override
                public void run() {
                    try {
                        for (int i = 0; i < 10 ; i++) {
                            noodles.makeNoodles();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"厨师A").start();
    
            new Thread(new Runnable(){
                @Override
                public void run() {
                    try {
                        for (int i = 0; i < 10 ; i++) {
                            noodles.eatNoodles();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"食客甲").start();
    
        }
    
    }
    

    输出如下:

    image
    可以见到是交替输出的;

    如果有两个厨师,两个食客,都进行10次循环呢?

    Noodles类的代码不用动,在主类中多创建两个线程即可,主类代码如下:

    public class Test {
    
        public static void main(String[] args) {
    
            Noodles noodles = new Noodles();
    
            new Thread(new Runnable(){
                @Override
                public void run() {
                    try {
                        for (int i = 0; i < 10 ; i++) {
                            noodles.makeNoodles();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"厨师A").start();
    
            new Thread(new Runnable(){
                @Override
                public void run() {
                    try {
                        for (int i = 0; i < 10 ; i++) {
                            noodles.makeNoodles();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"厨师B").start();
    
            new Thread(new Runnable(){
                @Override
                public void run() {
                    try {
                        for (int i = 0; i < 10 ; i++) {
                            noodles.eatNoodles();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"食客甲").start();
    
            new Thread(new Runnable(){
                @Override
                public void run() {
                    try {
                        for (int i = 0; i < 10 ; i++) {
                            noodles.eatNoodles();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"食客乙").start();
    
        }
    }
    

    此时输出如下:

    在这里插入图片描述

    虚假唤醒

    上面的问题就是"虚假唤醒"。
    当我们只有一个厨师一个食客时,只能是厨师做面或者食客吃面,并没有其他情况;
    但是当有两个厨师,两个食客时,就会出现下面的问题:

    1. 初始状态


      image
    2. 厨师A得到操作权,发现面的数量为0,可以做面,面的份数+1,然后唤醒所有线程;
      image
    3. 厨师B得到操作权,发现面的数量为1,不可以做面,执行wait操作;


      image
    4. 厨师A得到操作权,发现面的数量为1,不可以做面,执行wait操作;


      image
    5. 食客甲得到操作权,发现面的数量为1,可以吃面,吃完面后面的数量-1,并唤醒所有线程;
    image
    1. 此时厨师A得到操作权了,因为是从刚才阻塞的地方继续运行,就不用再判断面的数量是否为0了,所以直接面的数量+1,并唤醒其他线程;
    image
    1. 此时厨师B得到操作权了,因为是从刚才阻塞的地方继续运行,就不用再判断面的数量是否为0了,所以直接面的数量+1,并唤醒其他线程;


      image

      这便是虚假唤醒,还有其他的情况,读者可以尝试画画图分析分析。

    解决方法

    出现虚假唤醒的原因是从阻塞态到就绪态再到运行态没有进行判断,我们只需要让其每次得到操作权时都进行判断就可以了;
    所以将

    if(num != 0){
        this.wait();
    }
    

    改为

    while(num != 0){
        this.wait();
    }
    

    if(num == 0){
        this.wait();
    }
    

    改为

    while(num == 0){
        this.wait();
    }
    

    即可。

    微信搜索:code随笔 欢迎关注乐于输出Java,算法等干货的技术公众号。

    相关文章

      网友评论

          本文标题:Java多线程中的虚假唤醒和如何避免

          本文链接:https://www.haomeiwen.com/subject/dsbfhktx.html