《Java 并发编程实战》08 阅读笔记

149 阅读4分钟

重点是 MESA 模型。

管程(Monitor),指的是管理共享变量以及对共享变量的操作过程,让他们支持并发。也即管理类的成员变量和成员方法,让这个类是线程安全的。

在并发编程领域,有两大核心问题:一个是互斥,即同一时刻只允许一个线程访问共享资源;另一个是同步,即线程之间如何通信、协作。

这两大问题,管程都能解决。

管程将共享变量及其对共享变量的操作统一封装起来,以解决互斥问题;使用条件变量和条件变量等待队列解决线程同步问题。

Java 管程的实现参考了 MESA 管程模型(详见MESA管程模型):

  • 在管程模型里,共享变量和对共享变量的操作是被封装起来的。
  • 当多个线程同时试图进入管程内部时,只允许一个线程进入,其他线程则在入口等待队列中等待。
  • 管程里每个条件变量都对应有一个等待队列。

比如实现一个线程安全的阻塞队列,可以将线程不安全的队列封装起来,对外提供线程安全的操作方法,例如入队操作和出队操作。

那怎么解决同步问题呢?

假设有个线程 T1 执行阻塞队列的出队操作,有个前提条件,就是阻塞队列不能是空的。
如果线程 T1 进入管程后恰好发现阻塞队列是空的,此时线程 T1 就去“队列不空”这个条件变量的等待队列中等待。
线程 T1 进入条件变量的等待队列后,是允许其他线程进入管程的。

再假设之后另外一个线程 T2 执行阻塞队列的入队操作,入队操作执行成功之后,“阻塞队列不空”这个条件对于线程 T1 来说已经满足了,此时线程 T2 要通知 T1,告诉它需要的条件已经满足了。
当线程 T1 得到通知后,会从等待队列里面出来,但是出来之后不是马上执行,而是重新进入到入口等待队列里面。

下面的代码用管程实现了一个线程安全的阻塞队列。阻塞队列有两个操作分别是入队和出队,这两个方法都是先获取互斥锁,类比管程模型中的入口。

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();
    }  
  }
}

MESA 管程有一个编程范式,就是需要在一个 while 循环里面调用 wait()。这个是 MESA 管程特有的。

while(条件不满足) {
  wait();
}

MESA 管程里面,T2 通知完 T1 后,T2 还是会接着执行,T1 并不立即执行,仅仅是从条件变量的等待队列进到入口等待队列里面。这样做的好处是 notify() 不用放到代码的最后,T2 也没有多余的阻塞唤醒操作。但是也有个副作用,就是当 T1 再次执行的时候,可能曾经满足的条件,现在已经不满足了,所以需要以循环方式检验条件变量。

管程是一个解决并发问题的模型,理解这个模型的重点在于理解条件变量及其等待队列的工作原理。
Java 参考了 MESA 模型,语言内置的管程(synchronized)对 MESA 模型进行了精简。
MESA 模型中,条件变量可以有多个,Java 语言内置的管程里只有一个条件变量。
Java 内置的管程方案(synchronized)使用简单,synchronized 关键字修饰的代码块,在编译期会自动生成相关加锁和解锁的代码,但是仅支持一个条件变量;而 Java SDK 并发包实现的管程支持多个条件变量,不过并发包里的锁,需要开发人员自己进行加锁和解锁操作。