所谓虚假唤醒字面意思理解就是线程在被唤醒后,线程执行等待的变量条件实际上仍然不满足,这种情况发生在两个以上的多线程生产者消费者问题中。

从一个实际的例子中来理解虚假唤醒,建立一个简单的消费者生产者模型,判断条件时共享资源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