1.是什么
Java1.5之前,提供的唯一并发语言就是管程,1.5之后提供的SDK并发包也是以管程为基础。
管程:管理共享变量以及对共享变量操作的过程,让他们支持高并发。对于Java而言,就是管理类的状态变量,让这个类是线程安全的
synchronized关键字和wait()、notify()、notifyAll()这三个方法是Java中实现管程技术的组成部分。操作系统针对线程切换相关的还有信号量、PV等。
Java为什么不提供信号量这种编程原语,因为信号量能够解决所有并发问题。Java 采用的是管程技术,synchronized 关键字及 wait()、notify()、notifyAll() 这三个方法都是管程的组成部分。而管程和信号量是等价的,所谓等价指的是用管程能够实现信号量,也能用信号量实现管程。但是管程更容易使用,所以 Java 选择了管程。
管程出现过三种管程模型:
- Hasen模型
- Hoare模型
- MESA模型:目前广泛使用(Java的实现参考的也是该模型)
2.MESA模型
模型如下图:

管程是如何解决互斥和同步问题的呢?
2.1.解决互斥问题
前面说过了,管程是管理共享变量以及对共享变量操作,那我们该如何保证对共享变量的访问时互斥的呢?管程模型就采取了只允许一个线程进入,线程T1进入管程后,其余线程只能在入口等待队列进行等待
2.2.解决同步问题
先来回顾下同步的概念,同步是指线程之间进行协作,例如线程T1依赖于线程T2,只有线程T2执行完成线程T1才能执行
那么管程是如何解决并发下的同步问题的呢?
在上图我们能够看到除了入口等待队列,还存在条件变量等待队列,而且每个条件变量都对应有一个等待队列。而条件变量和条件变量等待队列就是用来解决同步问题的
例如:有一个线程T1进入管程中,但是突然发现某个条件(例如队列的出队操作,只有队列不空才能进行出队操作,而队列不空
就可以看作一个条件变量)不满足,此时线程T1就可以调用wait()方法,并进入条件变量的等待队列,然后线程T2进入了管程中或执行了一个操作,发现T1不满足的某个条件满足了,此时T2就可以调用notify()/notifyAll()通知条件变量等待队列中的某个线程或全部线程,此时T1再去重新去从入口进入管程执行方法。
这里我还是来一段代码再次说明一下吧。下面的代码实现的是一个阻塞队列,阻塞队列有两个操作分别是入队和出队,这两个方法都是先获取互斥锁,类比管程模型中的入口。
- 对于入队操作,如果队列已满,就需要等待直到队列不满,所以这里用了
notFull.await();
。
- 对于出队操作,如果队列为空,就需要等待直到队列不空,所以就用了
notEmpty.await();
。
- 如果入队成功,那么队列就不空了,就需要通知条件变量:队列不空
notEmpty
对应的等待队列。
- 如果出队成功,那就队列就不满了,就需要通知条件变量:队列不满
notFull
对应的等待队列。
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,可能线程被唤醒后,条件又不满足了,此时如果用while则会在阻塞解除之后再去判断条件,如果改成if直接往下执行,则会出现各种各样的问题
while(条件不满足) {
wait();
}
2.3.Hasen 模型、Hoare 模型和 MESA 模型的核心区别
三者的核心区别就是当条件满足后,如何通知相关线程。管程模型要求同一时刻只允许一个线程执行,那么线程T2的操作使线程T1等待的条件满足时,T1和T2究竟谁可以执行呢?
- Hasen模型:要求notify()放在代码的最后,这样T2执行完成之后,T2就结束了,T1此时再执行
- Hoare模型:阻塞唤醒,T2通知完之后就阻塞,T1执行完再唤醒T2
- MESA模型:T2通知完T1后,会继续执行,T1不会立即执行,仅仅是从条件变量的等待队列进入了入口等待队列中。但是有个副作用,就是T1再次执行时可能条件又不满足了。
2.4.何时使用notify()
需要满足如下三个条件:
- 所有等待线程拥有相同的等待条件(这里可能会有出现不同的场景,例如等待条件依赖于入参,那么不同线程的等待条件就可能不同)
- 所有等待线程被唤醒后,执行相同的操作
- 只需要唤醒一个线程
2.5.Java内置的管程-synchronized
其对MESA模型进行了精简,MESA 模型中,条件变量可以有多个,Java 语言内置的管程里只有一个条件变量。具体如下图所示。

Java内置的管程方案(synchronized)使用简单。synchronized 关键字修饰的代码块,在编译阶段会自动生成相关加锁和解锁的代码,但是仅支持一个条件变量,所以使用synchronized实际上只体现了管程的互斥,而同步却无法提现
Q:管程如何实现线程的互斥的呢?通过互斥量