前言
临界区、管程、信号量是操作系统对并发编程支持的三个概念。它们并不是属于java,而是属于操作系统(目前我知道 linux有这三个概念,我觉得windows也应该有只是不确定,否则怎么会支持多核CPU呢?)。
临界区
临界区是线程需要互斥执行的一段代码。临界区的资源是线程共享的,但是执行的返回结果是不确定的。 举个例子:linux系统中的fork()命令会开启一个子线程,每个子线程都应该去调用父线程的方法返回一个PID。假设这个方法是:
NEW_PID = next_pid++
上面的next_pid就是临界资源,是多个线程共享的, 这段代码不是原子操作,但是一定要是互斥的。 临界区只是一个概念,具体实现要看编程语言还操作系统。
信号量
信号量也是一个编程概念,基本由一个整数变量,和两个原子操作组成。这两个原子操作对这个整数变量对行增加和删除。成功才执行后面的代码,失败则阻塞。 具体代码实现可以看我的另一篇博客。
https://blog.csdn.net/u011296165/article/details/80249229
管程
管程对应的英文是Monitor。管程的定义是管理共享变量以及对共享变量的操作过程,让它们支持并发。相对应的java语意是管理java类的成员变量和成员方法,让这个类是线程安全的。
管程是编程语言提供的一种抽象数据结构,它有两个特点
- 互斥访问,即任一时刻只有一个线程在执行管程代码;
- 正在管程内的线程可以放弃对管程的控制权;
条件变量(condition variable)是管程内部的实现机制,每个条件变量都代表一种等待的原因,也对应一个等待队列。条件变量有两个操作:wait和signal。这两个条件变量的操作,是某种情况下等待和在某种情况下执行。
MESA模型----java管程使用的模型
管程实现互斥
管程实现互斥就是将共享变量和对共享变量操作的方法封闭起来。如下图,管程X将共享变量queue这个队列和相关操作入队enq()和deq()都封装起来。线程A和线程B如果想要访问共享变量queue()队列,就要使用enq()和deq()方法,enq()和deq()保持了互斥性,这样来保证线程安全。
管程实现同步
MESA的模型图
图中最大的方框就代表封装的意思, 上方开口就是管程的入口, 入口旁边有一个等待队列。一个管程只能有一个线程去执行,其他线程就进入到这个等待队列。 就像是去医院看医生,一个医生只能同时给一个病人看病,其他拿到号的病人都会在门外等着。
管程里面还有一个条件变量的概念,管程里面还有一个条件变量的概念,管程里面还有一个条件变量的概念这一定非常重要。 后面的东西都是使用了这个条件变量。每一个条件变量都有自己的等待队列,就如图上的条件变量A和条件变量B有两个等待队列。
现在对他的执行过程做一个说明。我们管程中通常两个方法,方法X代表出队操作,方法Y代表入队操作。我们的共享变量V就是队列。我们的假设线程T1在执行方法X出队操作,那么在执行这个出队操作之前是不是应用有一个前置条件,判断这个共享变量V队列不能为空。 而这个前置条件就是条件变量。条件变量的等待队列就是无法获取条件的时候,条件变量执行了wait()方法,将线程放入到了这个条件变量的等待队列中了。
举个生活中的例子:小明去医院看医生,第一步、进医院的就是去取号,然后去对应医生办公室门口等待叫号。这一步对应的就管程入口等待对列。 第二步 叫到号之后医生让小明去拍X光片,他就去抽血处排队抽血。这一步对应的就是条件变量等待对列。第三步 抽完血之后,小明又要去医生办公室门口等待叫号,这一步对应的就是当条件变量符合执行条件就从条件变量队列中释放,但并不是马上去执行,而是去管程入口队列中等待。第四步,小明进入医生办公室看医生,医生让小明在去验上血。小明就要去验血处等待。这一步对应的就是当线程第二次进入管程中,发现条件变量又不符合了,又要去重新进入条件变量队列中重新等待条件符合要求。 当符合要求后在次进入管程入口等待队列,直到进入管程后条件符合要求去执行方法完成。
这里可能有点绕,还请多看几遍。
下面用代码做一下示范。
假定对象A代表“队列不空”这个条件,那么线程进入管程需要判断队列是否为空,如果为空则调用 A.wait()方法。同理当“条件 不空”这个条件满足时,线程T2需要调用 A.notify()来通知A等待队列中的一个线程。 也可以调用 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();
}
}
}
- 对于入队操作,如果队列已满。就需要等待直接队列不满,所以这就用notFull.await()。
- 对于出队操作,如果队列为空,就需要等待直接到队不空,所以这就用notEmpty.await()。
- 如果入队成功,那么队列就不空,就需要通知队列不空的等待队列。这里就要用notEmpty.signal();
- 如果出队成功,那就队列就不满,就需要通知队列不满的等待队列。就这里要用notFull.signal();
总结
java MESA模型
java MESA 模型中只有一个条件变量,是使用synchronized关键字来实现的。而java sdk并发包中支持多个条件变量,需要开发人员手动去调用加锁和解锁操作。
注:
借鉴极客时间java并发编程和另外一篇博客,那篇博客因电脑突然关机找不到了,如发现请通知,我在加上链接。