管程
管程是什么
管程(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)