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

先来看一个例子

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

用代码说话

首先咱们须要有一个资源类,里面包含面的数量,作面操做,吃面操做;
当面的数量为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();

    }

}

输出以下:
能够见到是交替输出的;微信

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

Noodles类的代码不用动,在主类中多建立两个线程便可,主类代码以下:ide

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();

    }
}

此时输出以下:
在这里插入图片描述this

虚假唤醒

上面的问题就是"虚假唤醒"。
当咱们只有一个厨师一个食客时,只能是厨师作面或者食客吃面,并无其余状况;
可是当有两个厨师,两个食客时,就会出现下面的问题:线程

  1. 初始状态
  2. 厨师A获得操做权,发现面的数量为0,能够作面,面的份数+1,而后唤醒全部线程;
  3. 厨师B获得操做权,发现面的数量为1,不能够作面,执行wait操做;
  4. 厨师A获得操做权,发现面的数量为1,不能够作面,执行wait操做;
  5. 食客甲获得操做权,发现面的数量为1,能够吃面,吃完面后面的数量-1,并唤醒全部线程;

6. 此时厨师A获得操做权了,由于是从刚才阻塞的地方继续运行,就不用再判断面的数量是否为0了,因此直接面的数量+1,并唤醒其余线程;3d

7. 此时厨师B获得操做权了,由于是从刚才阻塞的地方继续运行,就不用再判断面的数量是否为0了,因此直接面的数量+1,并唤醒其余线程; 这即是虚假唤醒,还有其余的状况,读者能够尝试画画图分析分析。code

解决方法

出现虚假唤醒的缘由是从阻塞态到就绪态再到运行态没有进行判断,咱们只须要让其每次获得操做权时都进行判断就能够了;
因此将blog

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

改成图片

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

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

改成

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

便可。

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

相关文章
相关标签/搜索