什么是管程
管程是一种解决并发的概念,和信号量是等价的,管程能够去实现信号量,信号量也能实现管程
管程是直管理共享变量以及对共享变量的操作过程,让它们支持并发。
Java中的synchronized、wait()、notify()、notifyAll()就是用管程去实现的。
MESA模型
在管程发展史中,一共出现过三种模型:Hasen模型、Hoare模型、MESA模型。现在广泛应用的就是MESA模型,Java管程的实现也是参考的MESA模型。
在并发领域有两个核心问题:
- 互斥:同一时刻只允许一个线程访问共享资源。
- 同步:线程之间如果通信、协作。
这两个问题管程都是可以解决的。
管程解决互斥问题
管程将共享变量以及对共享变量的操作统一封装起来,对共享变量的操作都提供线程安全的方法。管程的模型和面向对象高度契合,就是利用封装的方法,把一个线程不安全的类封装成线程安全的。
管程解决同步问题
管程解决同步问题使用了条件变量的概念,每个条件变量都有一个等待队列。
图中, 最外层的框就相代表对共享变量的操作的封装。当一个线程想去操作共享变量时,如果有其他的线程正在执行,就必须进入入口等待队列,这个入口的控制就是互斥。对应于现实相当于就医流程中的排队挂号。
当线程进入管程(临界区)后,如果某个条件不满足时,就会进入条件变量的等待队列,然后其他线程时允许进入管程的。比如你去找医生做检查,医生需要你的验血报告,条件不满足,你需要去做验血报告,其他人可以继续去找医生就医。
等条件满足时,会再次进入入口的等待队列。
await()方法就是进入等待队列。
notify()、notifyAll()表示满足条件。
举个例子:实现一个线程安全的队列
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();
}
}
}
wait() 的正确姿势
但是有一点,需要再次提醒,对于 MESA 管程来说,有一个编程范式,就是需要在一个 while 循环里面调用 wait()。这个是 MESA 管程特有的。
while(条件不满足) {
wait();
}
因为当条件满足时,线程只是重新进入入口队列等待运行,在真正运行到的时候,条件可能又不满足了,所有需要用while循环判断条件是否满足
hasen 是执行完,再去唤醒另外一个线程。能够保证线程的执行。hoare,是中断当前线程,唤醒另外一个线程,执行玩再去唤醒,也能够保证完成。而mesa是进入等待队列,不一定有机会能够执行。
notify() 何时可以使用
还有一个需要注意的地方,就是 notify() 和 notifyAll() 的使用,前面章节,我曾经介绍过,除非经过深思熟虑,否则尽量使用 notifyAll()。那什么时候可以使用 notify() 呢?需要满足以下三个条件:
- 所有等待线程拥有相同的等待条件;
- 所有等待线程被唤醒后,执行相同的操作;
- 只需要唤醒一个线程。