今天看JDK文档中的Object.wait()方法,有一段提到:
对于某一个参数的版本,实现中断和虚假唤醒是可能的,而且此方法应始终在循环中使用:
synchronized (obj) { while (<condition does not hold>) obj.wait(); ... // Perform action appropriate to condition }
大致意思就是,wait()方法需要在循环中使用,以避免实现虚假唤醒。那什么是虚假唤醒,话不多说,贴一段代码来看:
-----------------------------------------------------代码分隔线-------------------------------------------------------
Thread-1:
while(true) {
obj = queue.get(); //2
// 3
}
Thread-2:
synchronized(lock) {
// 代码一
while(queue.isEmpty()) {
lock.wait();
// 4
}
obj = queue.get(); // 5
// 代码二(可能导致虚假唤醒)
lock.wait();
// 4
obj = queue.get(); // 5
}
Thread-3:
synchronized(lock) {
queue.add(obj);
lock.notify(); // 1
}
-----------------------------------------------------代码分隔线-------------------------------------------------------
从上面的伪代码看到:
一、有三个线程,对同一数据进行访问
二、线程1未对数据进行安全访问
三、初始状态为:queue.size()==0,代码执行顺序为: 1-->2-->3-->4-->5
四、线程2的目的是当queue为空时等待,不为空时取出数据进行处理
五、线程2的代码一、代码二只能使用其中一种方式。
结论:当线程2使用代码一时没有问题,当使用代码二时,此时线程被唤醒,但仍然取不到数据,这就是虚假唤醒。
虚假唤醒发生的条件为:
1、当一个数据存在三个及以上的线程竞争访问时(必要条件)
2、至少有一个线程没有对数据进行加锁访问(充分条件,使得虚假唤醒发生可能)
当满足两个条件才可能发生虚假唤醒。仅仅是可能,如果代码执行顺序为:1-->4-->5-->2-->3,线程二可以取到数据,也就不存在虚假唤醒。
解决虚假唤醒(以下任意一种方式即可):
1、所有的线程访问数据时都加锁(线程一就没有加锁)
2、在循环中等待(线程2中的代码一)