欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

为什么虚假唤醒可以通过 while避免 ,if却不能呢?

程序员文章站 2022-03-10 08:38:18
虚假唤醒虚假唤醒 Spurious wakeiups 指 :在线程的 等待/唤醒 的过程中,等待的线程被唤醒后,在条件不满足的情况依然继续向下运行了。Java官方给的Api 的代码块如下synchronized (obj) { while ( and ) { //.....省略..........

虚假唤醒<spurious wakeups>

-----写在前面: 最近学习java因为这个问题困扰了好几天,结果发现是因为一个特别明显的原因。因此写下这篇文章提醒自己! 与君共勉!

虚假唤醒 Spurious wakeiups 指 :在线程的 等待/唤醒 的过程中,等待的线程被唤醒后,在条件不满足的情况依然继续向下运行了。

Java官方给的Api 的代码块如下

synchronized (obj) {
         while (<condition does not hold> and <timeout not exceeded>) {
         	//.....省略.......
             obj.wait(timeoutMillis, nanos);
         }
     }

那么,为什么官方推荐使用 while 关键字 而不是 if 关键字呢?

while or if ?

我们先看以下代码

public class WhyWhile {

    //判断线程是否运行的条件,
    static int control;

    //锁对象;
    static final Object lock = new Object();

    //主线程;
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        //只有control == 1才打印的线程;
        new Thread(() -> {
            while (true) {
                synchronized (lock) {
                    **while** (control != 1) {
                        System.out.println("A wait 前");
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("A wait 后");
                    }
                    System.out.println("== 1");
                    lock.notifyAll();
                }
            }
        },"Thread-1").start();

        //只有control == 2才打印的线程;
        new Thread(() -> {
            **while** (true) {
                synchronized (lock) {
                    while (control != 2) {
                        System.out.println("B wait 前");
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("B wait 后");
                    }
                    System.out.println("== 2");
                    lock.notifyAll();
                }
            }
        },"Thread-2").start();

        while (true) {
            control = sc.nextInt();
            //输入后,通知所有需要lock的线程
            synchronized (lock) {
                lock.notifyAll();
            }
        }
    }
}

以上代码包含了 三个线程 :main,Thread-1(下文用 线程1 代替),Thread-2(下文用 线程2 代替)。以上代码简要如下

control == 1 线程1 打印 “ == 1”
control == 2 线程1 打印 “ == 2”
control != 1 and control != 2 线程 1 和 线程 2 进入到等待状态
main 线程是用来 设置 数字control,之后通知所有线程

运行以上代码;
输入1后,我们可以看到如下结果;
为什么虚假唤醒可以通过 while避免 ,if却不能呢?
这说明:当前 线程1线程2 都进入到了等待状态;

尝试输入一个1
为什么虚假唤醒可以通过 while避免 ,if却不能呢?
可以看到 输入1以后,线程1 被唤醒,从lock.wait()下一行开始运行,也就是从等待后的位置开始运行;

继续往下看结果,可以看到如下
为什么虚假唤醒可以通过 while避免 ,if却不能呢?
可以看到,线程2在次过程中也被 lock.notifyAll()唤醒过 (这里指 线程1 的代码块中的notifyAll)

如过我们把 线程2 中的 while 换成 if 呢?

        //只有control == 2才打印的线程;
        new Thread(() -> {
            while (true) {
                synchronized (lock) {
                //只把 while 修改成了 if
                   if (control != 2) {
                        System.out.println("B wait 前");
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("B wait 后");
                    }
                    System.out.println("== 2");
                    lock.notifyAll();
                }
            }
        }).start();

接下来我们再运行一次,同样输入 1;

为什么虚假唤醒可以通过 while避免 ,if却不能呢?

可以看见,有时候 线程2 也会被唤醒后输出结果; 这明显出现了 错误结果

错误结果分析

在进行错误结果分析前需要记住通过以上代码得到的两个结论:

  1. 线程被唤醒后,会从 wait() 处开始继续往下执行;
  2. while 被掉换成 if 后出现了虚假唤醒,出现了我们不想要的结果;

While 和 if 特点

if(condition){
	代码块.....
}
输出语句....

如上,if 判断condition为 true后,会
先执行代码块,再执行输出语句
先执行代码块,再执行输出语句
先执行代码块,再执行输出语句

通过这一条,我们分析 线程2 的while被替换成if后,以及替换前的代码的运行顺序;

首先是 if

                    if (control != 2) {
                        System.out.println("B wait 前");
                        try {
                        	//1.在这等待
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //2.被唤醒后输出"B wait 后";
                        System.out.println("B wait 后");
                    }
                    //3.跳出if输出"== 2";
                    System.out.println("== 2");
                    //4.通知其他需要锁的对象;
                    lock.notifyAll();

然后 while 呢?

					//3.回到while条件继续判断
                    while (control != 2) {
                        System.out.println("B wait 前");
                        try {
                        	//1.在这等待
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //2.被唤醒后输出"B wait 后";
                        System.out.println("B wait 后");
                    }
                    //运行不到,
                    System.out.println("== 2");
                    lock.notifyAll();

if 和 while 不同的判断逻辑让使用 while 可以避免虚假唤醒,因为唤醒后继续向下运行,还是需要再次判断条件。而 if 就 直接运行下去了,如果要使用 if 避免虚假唤醒,需要与else搭配使用(如下),或者直接把正确的输出语句放入if代码块中;

                    if (control != 2) {
                        System.out.println("B wait 前");
                        try {
                        	//1.在这等待
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //2.被唤醒后输出"B wait 后";
                        System.out.println("B wait 后");
                    }else{
                    	System.out.println("== 2");
                    	lock.notifyAll();
                    }

总结

  1. 线程如果进入等待状态,被唤醒后从wait()开始向下继续执行代码;
  2. 如果要用 if 判断线程的运行条件,最好与else相结合;

本文地址:https://blog.csdn.net/hgmolk/article/details/109855802