本文共 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 ArrayListlist = 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 操作,我们假设如下的步骤发生了:
-
A 拿到了锁 line 0。
-
A 发现 size==0, (line 1),然后进入等待,并释放锁 (line 2)。
-
此时 B 拿到了锁,line0,发现 size==0,(line 1),然后进入等待,并释放锁 (line 2)。
-
这个时候有个线程 C 往里面加了个数据 1,那么 notifyAll 所有的等待的线程都被唤醒了。
-
AB 重新获取锁,假设又是 A 拿到了。然后他就走到 line 3,移除了一个数据,(line4) 没有问题。
-
A 移除数据后想通知别人,此时 list 的大小有了变化,于是调用了 notifyAll (line5),这个时候就把 B 给唤醒了,那么 B 接着往下走。
-
这时候 B 就出问题了,因为其实此时的竞态条件已经不满足了 (size==0)。B 以为还可以删除就尝试去删除,结果就跑了异常了。
上面的这个现象就是虚假唤醒,在if条件下对于条件的判断,都是会触发虚假唤醒,因为持有锁去notify 唤醒在等待wait()的时候,if条件只会去判断一次,如果不通过while判断的化,实际上对于不满足条件下的唤醒操作都是无效操作。
对于这种问题的解决,按照官方文档的指示:都是通过将if条件修改成while
这也解释了为什么wait() 和notify() 方法一般都是在while(条件变量)代码体内
转载地址:https://blog.csdn.net/zcswl7961/article/details/112790266 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!