什么是管程?
管程其实操作系统中的一个概念,指的是管理共享变量以及对共享变量的操作过程,让他们支持并发,翻译成 Java 领域的语言,就是管理类的成员变量和成员方法,让这个类是线程安全的。它出现的目的就是为了用于安全地在多个线程之间共享资源。
那管程是如何解决线程间的同步问题呢?
管程通过入口等待队列和条件变量等待队列来控制访问共享变量和方法的顺序来实现多个线程之间共享资源。
在管程的发展史上,先后出现过三种不同的管程模型,分别是:Hasen 模型、Hoare 模型和 MESA 模型。其中,现在广泛应用的是 MESA 模型,并且 Java 管程的实现参考的也是 MESA 模型。所以我们重点介绍一下 MESA 模型。
MESA 模型
Mesa 模型通常与 Xerox PARC 在 20 世纪 70 年代开发的 Mesa 编程语言相关联,它引入了一种经典的线程同步和并发控制方法,尤其是在监视器的基础上进行改进。这个模型对后来的编程语言(如 Java)和并发理论有深远影响。
管程的理解:https://freegeektime.com/100023901/86089/
MESA 模型的背景
监视器(Monitor)基础
- 监视器是一种高级同步原语,包含共享数据和操作这些数据的同步方法。
- 它通过互斥锁(Mutex)和条件变量(Condition Variable)实现线程间的同步。
Mesa 的改进
在早期的监视器实现(如 Hoare 模型)中,条件变量的信号传递(signal)会导致线程立即切换,而 Mesa 模型引入了更灵活的机制,称为“非立即切换”模型。
MESA 模型的核心特点
Mesa 模型在并发控制中的关键特性包括:
(1) “Signal-and-Continue” 机制
- 在 Mesa 模型中,当一个线程通过条件变量发出信号(signal)时,它不会立即将控制权交给等待的线程,而是继续执行。
- 等待的线程被唤醒后,必须重新检查条件(因为条件可能在信号发出后又被修改)。
与 Hoare 模型的对比
- Hoare 模型:信号发出后,控制权立即交给等待线程,发出信号的线程被挂起。
- Mesa 模型:信号只是通知,发出信号的线程继续运行,等待线程稍后竞争锁。
示例伪代码
monitor MesaExample {
boolean resourceAvailable = false;
// 等待资源
procedure waitForResource() {
while (!resourceAvailable) {
wait(); // 等待条件变量
}
// 使用资源
}
// 释放资源
procedure releaseResource() {
resourceAvailable = true;
signal(); // 发出信号但继续执行
}
}
关键点:waitForResource
中的 while
循环是 Mesa 模型的标志,因为线程被唤醒后必须重新检查条件。
(2) 条件变量的灵活性
- Mesa 模型允许使用多个条件变量来管理复杂的同步需求。
- 每个条件变量可以关联不同的等待条件,提高并发控制的表达能力。
(3) 互斥与同步分离
- Mesa 模型将互斥(通过锁实现)和同步(通过条件变量实现)明确分开,使得代码更模块化。
MESA 模型适的使用场景
Mesa 模型适用于需要精细控制线程同步的并发系统。以下是几个典型场景:
(1) 生产者-消费者问题
生产者生成数据,消费者消费数据,中间通过缓冲区协调。
class BoundedBuffer {
private int[] buffer;
private int count = 0;
private final Object lock = new Object();
public BoundedBuffer(int size) {
buffer = new int[size];
}
public void produce(int item) throws InterruptedException {
synchronized (lock) {
while (count == buffer.length) { // 缓冲区满
lock.wait(); // 等待消费者消费
}
buffer[count++] = item;
lock.notify(); // 通知消费者,但生产者继续执行
}
}
public int consume() throws InterruptedException {
synchronized (lock) {
while (count == 0) { // 缓冲区空
lock.wait(); // 等待生产者生产
}
int item = buffer[--count];
lock.notify(); // 通知生产者,但消费者继续执行
return item;
}
}
}
- Mesa 特性:使用
while
检查条件,notify
不会立即切换线程。
(2) 读写锁问题
多个线程需要读数据,但写操作需要独占访问。
class ReadWriteLock {
private int readers = 0;
private boolean writer = false;
private final Object lock = new Object();
public void startRead() throws InterruptedException {
synchronized (lock) {
while (writer) {
lock.wait(); // 等待写者完成
}
readers++;
}
}
public void endRead() {
synchronized (lock) {
readers--;
if (readers == 0) {
lock.notifyAll(); // 通知写者
}
}
}
public void startWrite() throws InterruptedException {
synchronized ( volunteerslock) {
while (readers > 0 || writer) {
lock.wait(); // 等待读者和写者
}
writer = true;
}
}
public void endWrite() {
synchronized (lock) {
writer = false;
lock.notifyAll(); // 通知所有等待线程
}
}
}
- Mesa 特性:线程被唤醒后重新检查条件(如
readers > 0 || writer
)。
MESA 模型与现代技术的联系
Mesa 模型直接影响了现代编程语言的并发机制:
- Java:Java 的
synchronized
、wait()
、notify()
和notifyAll()
直接采用了 Mesa 模型的“Signal-and-Continue”语义。 - C#:
Monitor
类(如Monitor.Wait
和Monitor.Pulse
)也遵循类似设计。 - 操作系统:许多操作系统的线程库(如 POSIX 线程中的条件变量)借鉴了 Mesa 的思想。
为什么选择 Mesa 模型?
- 实现简单:无需复杂的线程切换逻辑。
- 灵活性:允许线程在发出信号后完成自己的工作。
- 健壮性:通过显式的条件检查避免虚假唤醒(spurious wakeup)。
MESA 模型总结
Mesa 模型是一种经典的并发控制机制,以其“Signal-and-Continue”和显式条件检查为核心,广泛应用于现代编程语言和系统中。它通过监视器和条件变量提供了线程安全的保障,尤其适合生产者-消费者、资源竞争等场景。