一. 什么是管程
管程(Monitors) 指的是管理共享变量以及对共享变量的操作过程,让他们支持并发。它是 Java 语言在1.5之前提供的唯一并发原语,而 Java 1.5之后提供的 SDK 并发包也是以管程技术为基础的。
二. 为什么使用管程
信号量机制本身存在着缺点:进程自备同步操作,P(S) 和 V(S) 操作大量分散在各个进程中,不易管理,易发生死锁。而管程封装了同步操作,对进程隐蔽了同步细节,简化了同步功能的调用界面,使得用户编写并发程序如同编写顺序(串行)程序一样简单。
引入管程机制的目的:
- 把分散在各进程中的临界区集中起来进行管理;
- 防止进程有意或无意的违法同步操作;
- 便于用高级语言来书写程序,也便于程序正确性验证。
三. 管程如何解决并发问题
在并发编程领域有两大核心问题:一个是互斥,即同一时刻只允许一个线程访问共享资源;另一个是同步,即线程之间如何通信和协作。这两个问题都可以通过管程来解决。
我们今天重点介绍一下现在最广泛应用的管程模型:MESA模型。
1. 管程对互斥问题的解决
如上图所示:
- 管程 X 封装了共享变量 queue 队列和入队 enq()、出队 deq() 操作;
- 线程 A 和线程 B 如果想访问共享变量 queue,只能通过调用管程提供的 enq()、deq() 方法来实现;
- enq()、deq() 具有互斥性,只允许一个线程进入管程。
2. 管程对同步问题的解决
最外层的框代表封装,当多个线程同时试图进入管程内部时,只允许一个线程进入,其它线程则需要在入口等待队列中等待。
管程里引入了条件变量的概念,每个条件变量都对应一个等待队列。比如上图中的条件变量 A 和条件变量 B 分别都有自己的等待队列。
-
假如存在一个线程 T1 需要执行出队操作,那只有队列不为空时才能成功执行。此时,队列不为空这个前提条件就是管程里的条件变量。
-
如果线程 T1 进入后发现队列是空的,就需要去“队列不空” 这个条件变量的等待队列中等待。
-
而当另外一个线程 T2 执行入队操作,操作执行成功之后,“队列不空”这个条件对于线程T1来说已经满足了,此时线程 T2 要通知 T1 ,告诉它需要的条件已经满足了。
-
当线程 T1 得到通知后,会从等待队列里面出来,但是出来之后不马上执行,而是重新进入入口等待队列里面进行等待。
其中,T1 去条件变量的等待队列中等待,是通过调用 wait() 实现的;
线程 T2 通知 T1 “队列不空” ,是通过调用 notify() 实现的。
notify() 和 notifyAll() 的区别在于,notify() 可以通知等待队列中的一个线程,而 notifyAll() 可以通知等待队列中的所有线程。
需要注意的地方:
1、对于 MESA 管程来说,有一个编程范式,就是需要在一个 while 循环里面调用 wait()。这个是 MESA 管程特有的。
while(条件不满足) {
wait();
}
2、尽量使用 notifyAll() 而不是 notify(),只有满足以下3种条件时,才能使用 notify():
- 所有等待线程拥有相同的等待条件;
- 所有等待线程被唤醒后,执行相同的操作;
- 只需要唤醒一个线程。
四. 管程的局限性
大多数常用的编程语言中没有实现管程,如果某种语言本身不支持管程,那么加入管程是很困难的。