Java并发:管程

2019/07/11

管程

管程是什么

管程(Monitor),指的是管理共享变量以及对共享变量的操作过程,让他们支持并发.

MESA模型

管程模型有 Hasen 模型、Hoare 模型和 MESA 模型。其中,现在广泛应用的是 MESA 模型,并且 Java 管程的实现参考的也是 MESA 模型。

并发需要解决两个核心:互斥、同步

解决互斥问题

管程的解决思路挺简单的:将共享变量和共享变量的操作统一封装起来。

管程 X 将共享变量 queue 这个队列和相关的操作入队 enq()、出队 deq() 都封装起来了;线程 A 和线程 B 如果想访问共享变量 queue,只能通过调用管程提供的 enq()、deq() 方法来实现;enq()、deq() 保证互斥性,只允许一个线程进入管程。

解决同步问题

下图中,框起来说明是封装,只有一个入口。

入口外有一个入口等待队列,当多个线程同时试图进入管程内部时,只允许一个线程进入,其他线程则在入口等待队列中等待。

管程里还引入了条件变量的概念,而且每个条件变量都对应有一个等待队列,条件变量 A 和条件变量 B 分别都有自己的等待队列。

例子

网上找的例子:(看不懂不要紧,看后来的理解)

假设有个线程 T1 执行出队操作,就是队列不能是空的,而队列不空这个前提条件就是管程里的条件变量。

如果线程 T1 进入管程后恰好发现队列是空的,就去条件变量对应的等待队列里面等。

线程 T1 进入条件变量的等待队列后,是允许其他线程进入管程的

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

然后完全没看懂这个例子在说啥

后来理解大概意思

  • T1所谓的执行出队操作,应该理解成比如T1要拿锁,但是锁拿不到。于是进入“可以拿锁”条件变量对应的等待队列中。
  • 这时候可以有T3进来看看能不能拿锁。
  • T2所谓的入队操作,应该理解成T2释放了锁,将“可以拿锁”这个变量变成true。这时候T1满足条件了不是立刻拿到锁,而是回到最初的大队列中和其它线程抢权限
  • 因为上一条,T1可能还是会拿不到锁,所以需要使用while实现
  • 其实就是和使用synchronized实现等待 - 通知机制的道理一样

关于wait()、notify()、notifyAll() 这三个操作。前面提到线程 T1 发现“队列不空”这个条件不满足,需要进到对应的等待队列里等待。这个过程就是通过调用 wait() 来实现的。如果我们用对象 A 代表“队列不空”这个条件,那么线程 T1 需要调用 A.wait()。同理当“队列不空”这个条件满足时,线程 T2 需要调用 A.notify() 来通知 A 等待队列中的一个线程,此时这个队列里面只有线程 T1。至于 notifyAll() 这个方法,它可以通知等待队列中的所有线程。

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

使用wait()的正确方法

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

Hasen 模型、Hoare 模型和 MESA 模型 的区别

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

什么时候用notify()

满足下面条件才用notify()

  • 所有等待线程拥有相同的等待条件;即while的条件
  • 所有等待线程被唤醒后,执行相同的操作;即while里的操作
  • 只需要唤醒一个线程。





github: https://github.com/Hikiy
作者:Hiki
创建日期:2019.07.11
更新日期:2019.07.11

(转载本站文章请注明作者和出处 Hiki

Post Directory