- 这段代码中为什么使用while(!isEmpty)而不是if(!isEmpty),这里引出虚假唤醒问题
public synchronized void push(String name,int age){
try {
//不能用 if,因为可能有多个线程,导致虚假唤醒
while(!isEmpty){//进入到while语句内,说明 isEmpty==false,那么表示有数据了,不能生产,必须要等待消费者消费
this.wait();//导致当前线程等待,进入等待池中,只能被其他线程唤醒
} 为什么使用while(!isEmpty)而不是if(!isEmpty)
//-------生产数据开始-------
this.name = name;
//延时代码
Thread.sleep(10);
this.age = age;
//-------生产数据结束-------
isEmpty = false;//设置 isEmpty 为 false,表示已经有数据了
this.notifyAll();//生产完毕,唤醒所有消费者
} catch (Exception e) {
e.printStackTrace();
}
}
-
官方解释什么是虚假唤醒:
线程也可以唤醒,而不会被通知,中断或超时,即所谓的虚假唤醒 。 虽然这在实践中很少会发生,但应用程序必须通过测试应该使线程被唤醒的条件来防范,并且如果条件不满足则继续等待。 换句话说,等待应该总是出现在循环中,就像这样:
synchronized (obj) {
while ()
obj.wait(timeout);
… // Perform action appropriate to condition
} -
举例说明什么是虚假唤醒:
(1)首先初始num为0,A线程将一个元素入队,此时num=1;
(2)B线程从队列中获取了一个元素,此时num = 0。
(3)D线程也想从队列中获取一个元素,但此时num = 0,D线程便只能进入阻塞(decrease.wait()),等待 num > 0。
(4)这时,C线程将一个元素入队,并调用incresement.notify()唤醒条件变量。
(5) 处于等待状态的D线程接收到C线程的唤醒信号,便准备解除阻塞状态,执行接下来的任务(获取队列中的元素)。
(6) 然而可能出现这样的情况:当D号线程准备获得队列的锁,去获取队列中的元素时,此时B号线程刚好执行完之前的元素操作,返回再去请求队列中的元素,B线程便获得队列的锁,检查到num > 0,就获取到了C号线程刚刚入队的元素,然后释放队列锁。
(7) 等到D线程获得队列锁,判断发现num == 0,B线程“偷走了”这个元素,所以对于D线程而言,这次唤醒就是“虚假”的,它需要再次等待num > 0。