虚假唤醒和你写的程序逻辑无关!

这是偏于底层的锅,是否看到有些博客写的虚假唤醒难以理解?各种逻辑看似合理,但却给出了虚假唤醒的错误解释?那么来看这篇文章吧,可以满足你的需求。
首先,先看一波《Java并发编程的艺术》中关于等待/通知的经典范式的描述:

等待方遵循如下原则:

  1. 获取对象的锁。
  2. 如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件。
  3. 条件满足则执行对应的逻辑。

对应的伪代码如下

synchronized(对象) { 
	while(条件不满足) { //(注意此处呼应第二条,使用while)
		对象.wait(); 
	}对应的处理逻辑 
}

通知方遵循如下原则:

  1. 获得对象的锁。
  2. 改变条件。
  3. 通知所有等待在对象上的线程。

对应的伪代码如下

synchronized(对象) { 
	改变条件 
	对象.notifyAll(); 
}

上面为什么唤醒后要再进行判断?书上并没有解释,但是经过仔细分析,我认为这是防止虚假唤醒,很多文章关于虚假唤醒的解释说有其他线程影响,导致条件再次改变,解释还说的通,但是这并不是虚假唤醒。

虚假唤醒是什么?来看看维基百科的解释:
Spurious wakeup
虚假唤醒描述了使用条件变量的复杂性,这些条件变量是由某些多线程API(如POSIX线程和Windows API)提供的。
即使从等待线程的角度来看,条件变量似乎已经发出了信号,但是等待的条件仍然可能是假的。其中一个原因是虚假的唤醒;也就是说,即使没有线程发出条件变量的信号,线程也可能从其等待状态中被唤醒。为了确保正确性,有必要在线程完成等待后验证条件是否确实为真。因为虚假的唤醒可以重复发生,这是通过在循环中等待来实现的,当条件为真时,该循环终止,例如:

true, for example:

/* In any waiting thread: */
while(!buf->full)
	wait(&buf->cond, &buf->lock);

/* In any other thread: */
if(buf->n >= buf->size){
	buf->full = 1;
	signal(&buf->cond);
}

在本例中,我们知道另一个线程将在发送buf->cond(同步两个线程的方法)之前将buf->full(等待的实际条件)设置。等待线程在从等待返回时将始终验证实际情况的真实性,以确保在出现虚假唤醒时正确的行为。
根据David R. Butenhof的POSIX线程编程ISBN 0-201-63392-2:
这意味着当您等待一个条件变量时,当没有线程特别广播或发出条件变量的信号时,等待可能(偶尔)返回。虚假的唤醒可能听起来很奇怪,但是在一些多处理器系统中,使条件唤醒完全可预测可能会大大降低所有条件变量操作的速度。造成虚假觉醒的竞争条件应该被认为是罕见的。”

知乎上有这样一个回答:
为什么条件锁会产生虚假唤醒现象(spurious wakeup)?
回答原文如下:

pthread 的条件变量等待 pthread_cond_wait 是使用阻塞的系统调用实现的(比如 Linux 上的
futex),这些阻塞的系统调用在进程被信号中断后,通常会中止阻塞、直接返回 EINTR 错误。同样是阻塞系统调用,你从 read 拿到
EINTR 错误后可以直接决定重试,因为这通常不影响它本身的语义。而条件变量等待则不能,因为本线程拿到 EINTR 错误和重新调用
futex 等待之间,可能别的线程已经通过 pthread_cond_signal 或者
pthread_cond_broadcast发过通知了。
所以,虚假唤醒的一个可能性是条件变量的等待被信号中断。不过,把等待放到循环里的另一个原因是还可能有这样的情况(有人觉得它是虚假唤醒的一种,有人觉得不是):明明有对应的唤醒,但条件不成立。这是因为可能由于线程调度的原因,被条件变量唤醒的线程在本线程内真正执行「加锁并返回」前,另一个线程插了进来,完整地进行了一套「拿锁、改条件、还锁」的操作。比如
Windows 下 SleepConditionVariableCS 的文档中就明确指出了可能出现这种情况。(pthread
里的情况则有点区别,人家的 pthread_cond_signal 本来就可能唤醒多个正在等待的线程。)

结论:

虚假唤醒与操作系统底层函数有关,而和逻辑无关,但是很多人分析的逻辑是这样:
明明有对应的唤醒,但条件不成立。这是因为可能由于线程调度的原因,被条件变量唤醒的线程在本线程内真正执行「加锁并返回」前,另一个线程插了进来,完整地进行了一套「拿锁、改条件、还锁」的操作。
个人认为,这不算虚假唤醒的解释,只能算是逻辑问题。

综上,关于虚假唤醒的浅解,希望能帮助到大家!