理解条件等待Condition

Condtion也被称为条件队列(condition queues)或者条件变量(condition variables),是利用线程间共享的全局变量进行同步的一种机制。主要包括两个动作:一个线程等待“条件谓词成立”而挂起;另一个线程使“条件谓词成立”,发出条件成立信号。由于对共享状态信息的访问发生在多线程中,因此条件变量的使用是需要结合锁机制的(一般是互斥锁)。也就是说,一个线程要等到临界区的共享数据达到某种状态时再进行某种操作,而这个状态的成立,则是由另外一个线程来完成后发送信号来通知的。

本文从条件等待的标准形式说开来,解释Condition相关的术语,等待队列图从直观的角度理解条件等待,最后会对比内置监视器队列和条件队列的异同点。主要包括以下内容:

  • 从条件等待的标准形式说开来
  • 专业术语
  • 锁、条件谓词、条件队列三者关系

从条件等待的标准形式说开来

1
2
3
4
5
6
7
8
9
获取锁;//①
while (条件状态不满足) {//②
释放锁;//③
线程挂起等待,直到条件满足通知;//④
重新获取锁;//⑤
}
操作临界区保护的共享数据;//⑥
释放锁;//⑦

以上是条件等待的标准形式,逐条解释一下:
①判断条件状态是否满足和操作共享数据必须在锁的保护下,保证多线程环境中同一时刻只能有一个线程修改条件状态和共享数据
②③④当条件状态不满足时,由于第1步中已经获取了,必须首先释放锁,进入等待唤醒,当前线程阻塞在④,其他线程才有机会获取锁后,修改条件状态,再唤醒等待线程。
⑤当前线程被唤醒后,需要首先获取锁,然后再次检查条件状态是否满足(原因见下文),如果再次检查发现条件状态仍然不满足(当前线程一定在想我一定遇到了一个假唤醒),那么当前线程继续重复②③④的逻辑
⑥如果当前线程备唤醒后再次检查条件状态已经满足(肯定是其他线程修改了条件状态,因为当前线程一直在等待,没有任何操作)。由于已经获得了锁,有了修改共享数据的资格。
⑦共享数据修改完后要释放掉锁,其他线程就可以尝试获取锁。就像买完火车票后就要离开售票窗口,这样后面排队的人才可以买票。

使用while而不使用if的原因

  1. 线程可能在不满足条件状态下被唤醒,这种称为虚唤醒。在这种情况下,被唤醒的线程必须重新检查条件,否则会出现在不满足条件状态下操作共享数据,这很危险。
  2. 使用notifyAll唤醒其他等待线程时,线程被唤醒到重新获取锁的间隔,其他线程已经获取锁并修改了条件状态,使得条件状态不满足。

专业术语

条件谓词,一个不知所云的概念。条件谓词是决定线程是等待还是执行的关键,所有等待的线程需要等待某种条件成真时才能执行,这种条件就是条件谓词。举个例子,水池有两个水管一个进水管一个出水管,当水池不满的时候,应当打开进水口,当水池满的时候应该关闭进水口。这里水池不满就是进水的条件谓词,反过来,水池不空就是打开出水的条件谓词。

一组线程会因为不满足同一个条件状态而处于等待状态,这就是条件队列。条件队列中的对象和锁对象是同一个对象,因为状态条件由锁保护,每次判断条件状态时都要先获取锁。

锁、条件谓词、条件队列三者关系

条件队列与锁的获取和释放
条件队列就像一座房子,这座房子有很多房间(ROOMS),锁就像进入就进入会员厅(VIP ROOM)的黄金钥匙,共享数据像藏在这座房子某一个房间中保险箱中。

①新建的线程会进入到一个叫做大厅(HALL ROOM)的房间,到这里的线程会第一次尝试获取锁,获取不到锁的线程阻塞在这里。

②有一个很幸运的线程获得了黄金钥匙,得以进入到会员厅(VIP ROOM),这个房间统一时间只能有一个线程进入。

③条件谓词是打开保险箱的白金钥匙,进入VIP ROOM的线程需要判断是否条件谓词是否为真,很不幸这个线程不满足条件谓词,进入一个叫做等待厅(WAITING ROOM)等待唤醒,在进入之前,释放了黄金钥匙。

④线程在等待厅中按照进入的先后顺序排列好等待唤醒。

⑤唤醒的线程再次尝试获取黄金钥匙并且很幸运又一次获取到黄金钥匙,再次判断条件谓词是否为真,如果为真,这个线程就可以打开保险箱操作里面的数据。

⑥操作完数据后,线程从出口走出这座房子,并把黄金钥匙放回会员厅的房间门口。

写作不易,痛并快乐着;理解可能存在偏差,句句斟酌推敲;抵制抄袭,践行原创技术之路。如果本文能对您有所帮助,实为荣幸,我是葛一凡。
微信公众号

参考

  1. Java Platform Standard Edition 7 Documentation
  2. 对条件变量(condition variable)的讨论
  3. 从零单排 Java concurrency, wait & notify
  4. 并行编程之条件变量(posix condition variables)
  5. C++11之多线程(三、条件变量)
  6. Linux线程同步之条件变量
  7. JAVA并发-条件队列