1. 引言
有这样一个场景:线程A在等待变量V符合某个条件,而这个变量V的状态要由另一个线程B去改变。
现在就会有一个关键问题:处于等待中的线程A,如何得知变量V变为了符合自己运行状态?
我们容易想到的一个方法是,让线程A不断地去检查变量V,如果不符合就一直检查,直到符合为止。这个方法是有效的,但是存在如下问题
- 线程A不停地检查,这会白白浪费CPU资源;
- 为了降低对CPU资源的耗费,可以让线程A每次检查之后睡眠一段时间,但是这又会导致难以确保及时性,因为如果条件在你睡眠的时候发生了变化,你不能及时感知到。
所以说,循环检测的方法是不理想的。那么有没有更好的方法呢?有,就是等待/通知机制
2. 等待/通知机制的使用
Java中的等待/通知机制,是指一个线程A为了等待变量V符合某个条件,调用了对象O的wait()方法后进入等待状态,加入到对象O的等待队列。而另一个线程B将变量V改编为A所需的状态后,调用对象O的notify()或者notifyAll()方法以通知线程A,线程A收到通知后从对象A的wait()方法返回,得以继续执行。线程A和B通过对象O来完成交互。注意,线程在调用对象O的wait()、notify()、notifyAll方法的时候,必须先获得对象O的锁,在调用之后要释放对象O的锁。
Java等待/通知的经典范式,伪代码:
等待方
synchronized(对象){
while(条件满足){
对象.wait();
}
对应的处理逻辑
}
通知方
synchronized(对象){
改变条件
对象.notify();
}
3. 等待/通知机制的底层原理
等待/通知机制不仅在Java语言中存在,在操作系统中也存在,准确地说,Java中的等待/通知机制就是基于操作系统的等待/通知机制实现的。这篇文章JVM源码分析之wait()和notify()分析了Java中wait()/notify()方法的源码,我这里直接给出结论:
Java的wait()方法直接调用了JVM的本地方法native wait();然后又兜兜转转调用了JVM的另一个本地方法park()来挂起线程;而在park方法中,就是调用了Linux操作系统的等待/通知机制。具体实现方法是条件变量+互斥锁。所以说线程实际上是阻塞在了底层的条件变量上,条件变量必须在互斥锁的配合下才能使用,详见条件变量和互斥锁
pthread_mutex_lock(_mutex) // 加锁
pthread_cond_wait(_cond, _mutex) // 使线程休眠
pthread_mutex_unlock(_mutex) // 解锁
条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(并给出条件成立信号)。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。——百度百科
同理,notify()方法直接就是一个本地方法,兜兜转转调用unpark()方法,最后调用Linux的pthread_cond_signal()方法唤醒阻塞在条件变量上的线程。
pthread_mutex_lock(_mutex) // 加锁
pthread_cond_signal(_cond) // 唤醒阻塞的线程
pthread_mutex_unlock(_mutex) // 解锁