并发编程(六):管程

【关于作者】

关于作者,目前在蚂蚁金服搬砖任职,在支付宝营销投放领域工作了多年,目前在专注于内存数据库相关的应用学习,如果你有任何技术交流或大厂内推及面试咨询,都可以从我的个人博客(https://0522-isniceday.top/)联系我

1.是什么

Java1.5之前,提供的唯一并发语言就是管程,1.5之后提供的SDK并发包也是以管程为基础。

管程:管理共享变量以及对共享变量操作的过程,让他们支持高并发。对于Java而言,就是管理类的状态变量,让这个类是线程安全的

synchronized关键字和wait()、notify()、notifyAll()这三个方法是Java中实现管程技术的组成部分。操作系统针对线程切换相关的还有信号量、PV等。

Java为什么不提供信号量这种编程原语,因为信号量能够解决所有并发问题。Java 采用的是管程技术,synchronized 关键字及 wait()、notify()、notifyAll() 这三个方法都是管程的组成部分。而管程和信号量是等价的,所谓等价指的是用管程能够实现信号量,也能用信号量实现管程。但是管程更容易使用,所以 Java 选择了管程。

管程出现过三种管程模型:

  1. Hasen模型
  2. Hoare模型
  3. MESA模型:目前广泛使用(Java的实现参考的也是该模型)

2.MESA模型

模型如下图:

img

管程是如何解决互斥同步问题的呢?

2.1.解决互斥问题

前面说过了,管程是管理共享变量以及对共享变量操作,那我们该如何保证对共享变量的访问时互斥的呢?管程模型就采取了只允许一个线程进入,线程T1进入管程后,其余线程只能在入口等待队列进行等待

2.2.解决同步问题

先来回顾下同步的概念,同步是指线程之间进行协作,例如线程T1依赖于线程T2,只有线程T2执行完成线程T1才能执行

那么管程是如何解决并发下的同步问题的呢?

在上图我们能够看到除了入口等待队列,还存在条件变量等待队列,而且每个条件变量都对应有一个等待队列。而条件变量和条件变量等待队列就是用来解决同步问题的

例如:有一个线程T1进入管程中,但是突然发现某个条件(例如队列的出队操作,只有队列不空才能进行出队操作,而队列不空就可以看作一个条件变量)不满足,此时线程T1就可以调用wait()方法,并进入条件变量的等待队列,然后线程T2进入了管程中或执行了一个操作,发现T1不满足的某个条件满足了,此时T2就可以调用notify()/notifyAll()通知条件变量等待队列中的某个线程或全部线程,此时T1再去重新去从入口进入管程执行方法。

这里我还是来一段代码再次说明一下吧。下面的代码实现的是一个阻塞队列,阻塞队列有两个操作分别是入队和出队,这两个方法都是先获取互斥锁,类比管程模型中的入口。

  1. 对于入队操作,如果队列已满,就需要等待直到队列不满,所以这里用了notFull.await();
  2. 对于出队操作,如果队列为空,就需要等待直到队列不空,所以就用了notEmpty.await();
  3. 如果入队成功,那么队列就不空了,就需要通知条件变量:队列不空notEmpty对应的等待队列。
  4. 如果出队成功,那就队列就不满了,就需要通知条件变量:队列不满notFull对应的等待队列。
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() 的正确姿势

但是有一点,需要再次提醒,对于 MESA 管程来说,有一个编程范式,就是需要在一个 while 循环里面调用 wait()。这个是 MESA 管程特有的。因为如果这里不用while,可能线程被唤醒后,条件又不满足了,此时如果用while则会在阻塞解除之后再去判断条件,如果改成if直接往下执行,则会出现各种各样的问题

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

2.3.Hasen 模型、Hoare 模型和 MESA 模型的核心区别

三者的核心区别就是当条件满足后,如何通知相关线程。管程模型要求同一时刻只允许一个线程执行,那么线程T2的操作使线程T1等待的条件满足时,T1和T2究竟谁可以执行呢?

  1. Hasen模型:要求notify()放在代码的最后,这样T2执行完成之后,T2就结束了,T1此时再执行
  2. Hoare模型:阻塞唤醒,T2通知完之后就阻塞,T1执行完再唤醒T2
  3. MESA模型:T2通知完T1后,会继续执行,T1不会立即执行,仅仅是从条件变量的等待队列进入了入口等待队列中。但是有个副作用,就是T1再次执行时可能条件又不满足了。

2.4.何时使用notify()

需要满足如下三个条件:

  1. 所有等待线程拥有相同的等待条件(这里可能会有出现不同的场景,例如等待条件依赖于入参,那么不同线程的等待条件就可能不同)
  2. 所有等待线程被唤醒后,执行相同的操作
  3. 只需要唤醒一个线程

2.5.Java内置的管程-synchronized

其对MESA模型进行了精简,MESA 模型中,条件变量可以有多个,Java 语言内置的管程里只有一个条件变量。具体如下图所示。

image-20210811203555719

Java内置的管程方案(synchronized)使用简单。synchronized 关键字修饰的代码块,在编译阶段会自动生成相关加锁和解锁的代码,但是仅支持一个条件变量,所以使用synchronized实际上只体现了管程的互斥,而同步却无法提现

Q:管程如何实现线程的互斥的呢?通过互斥量

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

哈哈哈张大侠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值