并发编程已经发展很多年,自分时OS以来就已经存在,相关的理论和技术非常多。 这其中的管程是解决并发编程的关键技术。
什么是管程
指的是管理共享变量以及对共享变量的操作过程,让它们支持并发。 在Java领域即管理类的成员变量和方法,让这个类是线程安全的。
管程的英文是:Monitor, 直译是监视器,在OS领域可意译为管程。
管程(monitor) vs 信号量(semophore)
在OS课程中,我们都会学到信号量,知道可用信号量实现互斥锁和同步变量,分别用来解决并发的两大核心问题:互斥和同步(线程间通信,协作)。而管程和信号量其实是等价的,即:可用信号量实现管程,也能用管程实现信号量。但管程更易使用,所以jdk选择了管程。
MESA模型
在管程的发展史上,先后出现过三种不同的管程模型,分别是:Hasen 模型、Hoare 模型和 MESA 模型。现在广泛应用的是 MESA 模型,Java管程以MESA为参考实现。
管程如何解决互斥问题
管程解决互斥问题的思路就是将共享变量及其对共享变量的操作统一封装起来,同一时刻只允许一个线程进入管程。
管程如何解决线程间同步问题
通过在管程中引入条件变量的概念,且每个条件变量都对应一个等待队列,此处的原理跟信号量实现的条件变量类似。 下面示例是一个阻塞队列的代码实现,其中用到了条件变量。
class BlockingQueue {
final Lock lock = new ReentrantLock();
// 条件变量:队列不满
final Condition notFull = lock.newCondition();
// 条件变量:队列不空
final Condition notEmpty = lock.newCondition();
List<Object> internalQueue = new ArrayList<>();
void enq(Object o) throws InterruptedException {
lock.lock();
try {
// 队列已满
while (internalQueue.size() >= 5) {
notFull.await();
}
// 入队
internalQueue.add(o);
// 只要入队了说明当前队列不空,唤醒出列线程
notEmpty.signal();
} finally {
lock.unlock();
}
}
void deq() throws InterruptedException {
lock.lock();
try {
// 当前队列为空,线程在非空条件变量队列等待
while (internalQueue.size() == 0) {
notEmpty.await();
}
internalQueue.remove(0);
// 只要出列了说明当前队列不满,唤醒入列线程
notFull.signal();
} finally {
lock.unlock();
}
}
}
注意判断队列满或空的代码段,使用的是while,原因是调用条件变量的signal(),被唤醒的目标线程并非立即执行,且执行时是获取到互斥锁并且从wait后一行开始,所以当它执行时可能条件又不再满足,所以需要再次做判断。
总结
Java语言本身对管程的实现做了精简,即只有一个条件变量,且通过synchronized关键字实现不需要显式打开和关闭互斥锁。通过JDK1.5后的并发包可以支持多个条件变量,但需要显式打开和关闭互斥锁。
编程语言中的两大问题互斥和同步都可以通过管程来实现,很多并发工具的底层类也是基于它们实现。