wait和notify的虚假唤醒(spurious wakeups)
发布日期:2021-06-29 03:44:50 浏览次数:3 分类:技术文章

本文共 1769 字,大约阅读时间需要 5 分钟。

文章目录


1 现象

这个词的定义来源于JDK的Object#wait()方法的注解

在这里插入图片描述
官方API明确的告诉我们,为了防止发生中断错误以及虚假唤醒的问题,我们需要将wait()方法放在while() 循环中使用

synchronized (obj) {     while (<condition does not hold>) {	     	obj.wait();	}      ... // Perform action appropriate to condition}

实际上在我们去常使用Object的wait()(notify,notifyAll) 方法的时候,也必然伴随着如官方文档那样的标准的写法(while),那么,问题来了,什么是虚假唤醒(spurious wakeups)

2 虚假唤醒

虚假唤醒(spurious wakeups)的现象一般是伴随着当另一个持有了锁的线程去notify(或者是notifyAll())的时候,锁等待(状态:Thread.State = WAITING)在wait()方法上的线程会被唤醒,并重新竞争锁,竞争到锁的线程会继续执行wait()方法之后的代码【注意这里不是重新去执行一遍锁方法】

这里我们举一个例子:(该代码示例来源于网站)

static class Buf {    private final int MAX = 5;    private final ArrayList
list = new ArrayList<>(); synchronized void put(int v) throws InterruptedException { if (list.size() == MAX) { wait(); } list.add(v); notifyAll(); } synchronized int get() throws InterruptedException { // line 0 if (list.size() == 0) { // line 1 wait(); // line2 // line 3 } int v = list.remove(0); // line 4 notifyAll(); // line 5 return v; } synchronized int size() { return list.size(); }}

假设现在有 A,B 两个线程来执行 get 操作,我们假设如下的步骤发生了:

  1. A 拿到了锁 line 0。

  2. A 发现 size==0, (line 1),然后进入等待,并释放锁 (line 2)。

  3. 此时 B 拿到了锁,line0,发现 size==0,(line 1),然后进入等待,并释放锁 (line 2)。

  4. 这个时候有个线程 C 往里面加了个数据 1,那么 notifyAll 所有的等待的线程都被唤醒了。

  5. AB 重新获取锁,假设又是 A 拿到了。然后他就走到 line 3,移除了一个数据,(line4) 没有问题。

  6. A 移除数据后想通知别人,此时 list 的大小有了变化,于是调用了 notifyAll (line5),这个时候就把 B 给唤醒了,那么 B 接着往下走。

  7. 这时候 B 就出问题了,因为其实此时的竞态条件已经不满足了 (size==0)。B 以为还可以删除就尝试去删除,结果就跑了异常了。

上面的这个现象就是虚假唤醒,在if条件下对于条件的判断,都是会触发虚假唤醒,因为持有锁去notify 唤醒在等待wait()的时候,if条件只会去判断一次,如果不通过while判断的化,实际上对于不满足条件下的唤醒操作都是无效操作。

对于这种问题的解决,按照官方文档的指示:都是通过将if条件修改成while

这也解释了为什么wait() 和notify() 方法一般都是在while(条件变量)代码体内

转载地址:https://blog.csdn.net/zcswl7961/article/details/112790266 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Java的值传递特性
下一篇:架构核心原理

发表评论

最新留言

初次前来,多多关照!
[***.217.46.12]2024年04月30日 15时47分27秒