所谓虚假唤醒字面意思理解就是线程在被唤醒后,线程执行等待的变量条件实际上仍然不满足,这种情况发生在两个以上的多线程生产者消费者问题中。
从一个实际的例子中来理解虚假唤醒,建立一个简单的消费者生产者模型,判断条件时共享资源number是否等于0,等于0时,生产者让其+1,不等于0时,消费者让其-1。
public class PretendNotify {
public static void main(String[] args) {
ShareDate shareDate = new ShareDate();
for (int i = 0; i < 3; i++) {
new Thread(()->{
shareDate.produce();
},String.valueOf(i)).start();
}
for (int i = 0; i < 3; i++) {
new Thread(()->{
shareDate.consume();
},String.valueOf(i)).start();
}
}
}
//共享资源类
class ShareDate {
private int number = 0;
Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
// 生产
public void produce() {
lock.lock();
try {
if (number != 0) {
// 不满足生产条件,挂起线程
System.out.println(Thread.currentThread().getName()+"不满足生产条件,暂时挂起");
condition.await();
}
// 执行生产任务并唤醒消费线程
number++;
System.out.println(Thread.currentThread().getName()+"执行了生产任务,此时number="+number);
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
// 消费
public void consume() {
lock.lock();
try {
if (number == 0) {
// 不满足消费条件,挂起线程
condition.await();
}
// 执行消费任务并唤醒消费线程
number--;
System.out.println(Thread.currentThread().getName()+"执行了消费任务,此时number="+number);
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
运行一下,结果如下:
0执行了生产任务,此时number=1
2不满足生产条件,暂时挂起
1不满足生产条件,暂时挂起
0执行了消费任务,此时number=0
2执行了生产任务,此时number=1
1执行了生产任务,此时number=2
1执行了消费任务,此时number=1
2执行了消费任务,此时number=0
这里问题就出现了,正常情况下应该是生产线程执行一次,消费线程执行一次,而此时却出现了munber=2的情况,从控制台的结果可以看出执行的过程,0号生产者线程在执行完生产任务后,2号生产者线程获得了锁,由于此时的number并没有被消费,所以判断条件 if (number == 0)为false,2号生产线程被挂起,同样的1号生产线程获得了锁,此时number还没有被消费,所以也被挂起。
然后0号消费者线程执行了消费任务,唤醒了生产者线程,被挂起的1号和2号生产者线程被挂起的位置是在执行了if判断语句之后,所以被唤醒的时候直接就执行了生产任务,而不会再经过判断,这是number=2出现的原因。
在这里,线程1就是被虚假唤醒的,因为生产者线程1和线程2同时被唤醒后,线程2先执行了生产任务,这个时候线程1其实是不满足生产条件的,也就是被虚假唤醒的。解决方法在JDK文档中也给出了
synchronized (obj) {
while (<condition does not hold>)
obj.wait();
... // Perform action appropriate to condition
}
也就是使用while循环判断,线程被唤醒后要再次验证判断条件再执行,将上面的demo中的if()判断修改后,再次运行结果为:
0执行了生产任务,此时number=1
1不满足生产条件,暂时挂起
2不满足生产条件,暂时挂起
0执行了消费任务,此时number=0
1执行了生产任务,此时number=1
2不满足生产条件,暂时挂起
1执行了消费任务,此时number=0
2执行了生产任务,此时number=1
2执行了消费任务,此时number=0