# 并发理论:管程和信号量

在这里插入图片描述

# 介绍

在操作系统中,信号量(Semaphore)和管程(Monitor)是两种最核心的同步机制,用于解决多线程或多进程在并发执行时的互斥(不能同时访问临界资源)和同步(按特定顺序执行)问题

# 信号量

信号量由操作系统巨擘 Dijkstra 提出,本质上是一个整型变量,配合两个原子操作(不可中断的操作)来控制并发。这两个操作最初被称为 PV(源自荷兰语),现在通常被称为 wait 和 signal

# 核心机制

计数器的含义:信号量的值 SS 通常表示可用资源的数量。

P 操作 (wait / down):想要消费资源时执行。 S = S - 1 如果 S < 0,说明没资源了,该线程进入阻塞队列等待。

V 操作 (signal / up):释放资源时执行。 S = S + 1 如果 S <= 0,说明有线程在排队,唤醒阻塞队列中的一个线程。

在这里插入图片描述

# 信号量的分类

二值信号量:值只能是 0 或 1。作用类似于一把锁,主要用于实现互斥

计数信号量:值可以大于 1。用于管理多个同类资源(例如:限流、连接池)

缺点:信号量非常强大、灵活,但就像 C 语言的手动内存管理一样,极易出错。程序员必须小心翼翼地配对 P 和 V 操作。如果漏写一个 V,就会导致死锁;如果写错顺序,整个同步机制就会崩溃

# 管程

为了解决信号量“容易写错”的问题,霍尔(C.A.R. Hoare)和汉森(Brinch Hansen)提出了管程。

管程是一种面向对象思想的同步机制。它把共享变量和对这些变量的所有操作(函数)全封装在一个特殊的“盒子”(类/模块)里

# 核心特性

管程中的被迫互斥:管程有一个硬性规则——在任意时刻,只能有一个线程在管程内部执行。其他想进入管程的线程必须在管程外的入口队列(Entry Queue)中排队。这直接由编译器或语言底层保证,程序员不需要手动加锁。

条件变量:为了解决线程在管程内部“因为某些条件不满足而必须等待”的问题,管程引入了条件变量。

wait() 操作:当线程发现条件不满足(例如缓冲区满了),它就调用 x.wait(),主动释放管程的控制权,并进入条件 xx 的等待队列

signal() 操作:当另一个线程让条件满足了(例如拿走了一个数据),它就调用 x.signal(),唤醒在 xx 队列中等待的一个线程

在这里插入图片描述

# 管程在Java中的应用

Java 语言的设计深度依赖管程模型。在 Java 中,每一个对象(Object)在诞生时,其内部都会隐式附带一个管程(通常被称为 Monitor / 内置锁)

# 隐式管程:synchronized 关键字

这是 Java 中最常见的管程实现。当你使用 synchronized 关键字时,Java 编译器和 JVM 会自动帮你处理管程的进入和退出

public class BoundBuffer {
    private final Object lock = new Object(); // 充当管程的对象
    private int count = 0;

    public void produce() throws InterruptedException {
        synchronized(lock) { // 进入管程(自动获取锁)
            while (count == 10) {
                lock.wait(); // 条件不满足,线程释放管程并进入等待队列
            }
            count++;
            lock.notifyAll(); // 唤醒其他等待的线程
        } // 退出管程(自动释放锁)
    }
}

入口队列(Entry Set):所有等待进入 synchronized 代码块的线程都会在这里排队

等待队列(Wait Set):调用了 lock.wait() 的线程会释放锁,并进入这个专门的条件等待队列,直到被 notify() 唤醒

# 显式管程:ReentrantLock + Condition

为了提供更灵活的控制(例如支持多个等待条件),Java 在 java.util.concurrent.locks 包中提供了显式管程

ReentrantLock 充当管程的互斥锁。Condition 充当管程的条件变量(一个管程可以绑定多个条件)

ReentrantLock lock = new ReentrantLock();
Condition notFull  = lock.newCondition(); // 队列不满条件(独立等待队列1)
Condition notEmpty = lock.newCondition(); // 队列不空条件(独立等待队列2)

synchronized就是基于管程实现的,只包含一个同步队列,一个等待队列 AQS也是基于管程实现的,只包含一个同步队列,但是可以包含多个等待队列