参考原文:管程——并发万能钥匙
什么是管程
所谓管程,指的是管理共享变量以及对共享变量的操作过程,让他们支持并发。Java采用的是管程技术,Synchronized关键字及wait()、notify()、notifyAll()这三个方法都是管程的组成部分。
管程是一种概念,任何语言都可以使用,在Java中每个加锁的对象都绑定着一个管程。
MESA模型
在管程的发展史上,先后出现过三种不同的管程模型,分别是:Hasen模型、Hoare模型和MESA模型,目前广泛使用的是MESA模型,并且Java管程的实现参考也是MESA模型。
管程作为并发编程的基本心法,主要解决两大核心问题:一个是互斥,即同一时刻只允许一个线程访问共享资源;另一个是同步,即线程之间如何通信、写作。
如何解决互斥问题
管程解决互斥问题的思路很简单,就是将共享变量及其对共享变量的操作同一封装起来。加入想要实现一个线程安全的阻塞队列,将线程不安全的队列封装起来,对外提供线程安全的操作方法,如入队或出队操作。
如何解决同步问题
在管程模型里,共享变量和共享变量的操作是被封装起来的,图中最外层的框就代表封装的意思,框的上面只有一个入口,并且在入口旁边还有一个入口等待队列。当多个线程同时试图进入管程内部的时候,只允许一个线程进入,其他线程则在入门等待队列中等待。类比就医流程的分诊,只允许一个患者就诊,其他患者都在门口等待。
管程里还引入了条件变量的概念,而且每个条件变量都对应由一个等待队列,如下图,条件变量A和条件变量B分别都有自己的等待队列。
其中条件变量和条件变量等待队列的作用就是解决线程同步问题。
假设想基于管程实现一个阻塞队列;
线程T1执行阻塞队列的出队操作,执行出队操作的前提条件是阻塞队列不能为空,这个条件对应的就是管程里的条件变量。对于线程T1如果目前阻塞队列为空,那么它就会进入管程里该条件变量对应的条件等待队列中等待条件满足后被唤醒;此时,线程T2执行阻塞队列的入队操作;T2执行成功后,阻塞队列不空这个条件对于T1来说就已经满足了,此时T2要通知T1,告诉它需要的条件已经满足,当线程T1得到通知之后,就会从等待队列中出来,但是出来之后不是立马执行,而是重新进入到入口等待队队列中。
public class BlockedQueue<T>{
final Lock lock =
new ReentrantLock();
// 条件变量:队列不满
final Condition notFull =
lock.newCondition();
// 条件变量:队列不空
final Condition notEmpty =
lock.newCondition();
// 入队
void enq(T x) {
lock.lock();
try {
while (队列已满){
// 等待队列不满
notFull.await();
}
// 省略入队操作...
//入队后,通知可出队
notEmpty.signal();
}finally {
lock.unlock();
}
}
// 出队
void deq(){
lock.lock();
try {
while (队列已空){
// 等待队列不空
notEmpty.await();
}
// 省略出队操作...
//出队后,通知可入队
notFull.signal();
}finally {
lock.unlock();
}
}
}
这里使用了Java并发包里的Lock和Condition,都是基于MESA管程的思想来实现的。
wait()的正确姿势
对于MESA有一个独有的编程范式,就是需要在一个while循环里调用wait()方法;
while(条件不满足){
wait();
}
- Hasen模型里,要求notify()放在代码的最后,这样T2通知完T1后,T2旧结束了,然后T1在执行,这样就能保证同一时刻只有一个线程执行;
- Hoare模型里,T2通知完T1后,T2阻塞,T1马上执行;等T1执行完再唤醒T2,也能保证同一时刻只有一个线程执行,但是T2多了一次唤醒;
- MESA管程里,T2通知完T1后,T2还是继续执行,而T1则从条件变量等待队列进入入口等待队列里,重新排队。这样notify()不用放到代理最后,而T2也没有多余的阻塞唤醒。但是有个副作用,就是当T1再次执行的时候,可能曾经满足的条件现在已经不满足了,所以需要以循环方式检验条件变量。
notify()何时使用
对于notify和notifyAll,除非满足下面条件,否则尽量使用notifyAll:
- 所有等待线程拥有相同的等待条件;
- 所有等待线程被唤醒后执行相同操作;
- 只需唤醒一个线程。
总结
Java参考MESA模型,语言内置的管程(synchronized)对MESA模型进行了精简,MESA模型中条件变量可以有多个,Java语言内置的管程里只有一个条件变量。