Java并发 -- 管程
概述
- Java语言在1.5之前,唯一提供的并发原语是管程
- 在Java 1.5提供的JUC包中,也是以管程技术为基础的
- 管程是一把解决并发问题的万能钥匙
管程
- 在Java 1.5之前,仅仅提供synchronized关键字和wait/notify/notifyAll方法
- Java采用的是管程技术,synchronized关键字以及wait/notify/notifyAll方法都是管程的组成部分
- 管程和信号量是等价的(即用管程能实现信号量,用信号量也能实现管程),但管程更容易使用,所以Java选择了管程
- Monitor,在Java领域会翻译成监视器,在操作系统领域会翻译成管程
- 管程:_管理共享变量以及对共享变量的操作过程,让它们支持并发_
- 对应Java领域:管理类的成员变量和成员方法,让这个类是线程安全的
MESA模型
- 在管程的发展史上,先后出现了三种不同的管程模型,分别是:Hasen模型、Hoare模型和MESA模型
- 现在广泛应用的是MESA模型,Java管程的实现也参考了MESA模型
- 管程可以解决并发领域的两大核心问题:_互斥+同步_
- 互斥:在同一时刻只允许一个线程访问共享资源
- 同步:线程之间如何通信、协作
互斥
- 管程解决互斥问题的思路:将共享变量以及对共享变量的操作统一封装起来
- 管程X将共享变量queue和相关的操作enq()和deq()都封装起来
- 线程A和线程B如果想要访问共享变量queue,只能通过调用管程X提供的enq()和deq()方法来实现
- enq()和deq()保持互斥性,只允许一个线程进入管程X
- 管程模型与面向对象高度契合
同步
- 在管程模型里,共享变量和对共享变量的操作是被封装起来的,最外层的框是代表封装的意思
- 框的上面只有一个入口,并且在入口旁边还有一个_入口等待队列_
- 当多个线程同时试图进入管程内部时,只允许一个线程进入,其他线程就在入口等待队列中等待
- 管程里还引入了条件变量的概念,_每个条件变量都对应一个等待队列_
- 条件变量和其对应等待队列的作用:线程同步
实例:出队入队
1 | // 下列三对操作的语义是相同的 |
- 假设线程T1执行出队操作,执行出队操作的前提条件是队列不空,而队列不空就是管程里的条件变量
- 如果线程T1进入管程后恰巧发现队列为空,就会到队列不空这个条件变量的等待队列里等待
- 当线程T1进入条件变量的等待队列后,是允许其他线程进入管程的
- 再假设线程T2执行入队操作,执行成功后,队列不空这个条件对于线程T1来说是已经满足了的,线程T2会通知线程T1
- 当线程T1得到通知后,会从等待队列里面出来,但不能马上执行,需要重新进入到入口等待队列
编程范式
- 对于MESA管程,有一个编程范式:
while(条件不满足){wait();}
,这是MESA管程特有的 - Hasen模型、Hoare模型和MESA模型的核心区别:_当条件满足时,如何通知相关线程_
- 管程要求同一时刻只允许一个线程执行,当线程T2的操作使线程T1等待的条件满足时
- Hasen模型:要求notify()放在代码的最后,这样T2通知完T1后,T2也就结束了,然后T1再执行
- 缺点:不灵活
- Hoare模型:T2通知完T1后,T2阻塞,T1马上执行,等T1执行完,再唤醒T2
- 缺点:相比Hasen模型模型,多了一次阻塞唤醒操作
- MESA模型:T2通知完T1后,T2接着执行,T1不会立即执行,仅仅是从条件变量的等待队列进入到入口等待队列
- 优点:notify()不用放在代码的最后,也没有多余的唤醒阻塞操作
- 缺点:当T1再次执行的时候,曾经满足的条件可能已经不满足了,所以才有上面特有的编程范式
- Hasen模型:要求notify()放在代码的最后,这样T2通知完T1后,T2也就结束了,然后T1再执行
notify的使用场景
- 一般情况下,_尽量使用notifyAll()_
- 满足3个条件,也可以使用notify()
- 所有等待线程拥有相同的等待条件
- 所有等待线程被唤醒后执行相同的操作
- 只需要唤醒一个线程
Java的管程实现
- Java参考了MESA模型,语言内置的管程(synchronized)对MESA模型进行了精简
- 在MESA模型中,条件变量可以有多个,_但Java语言内置的管程只有一个条件变量_
- Java内置的管程方案(synchronized)使用很简单
- synchronized关键字修饰的代码块,在编译期会自动生成相关加锁和解锁的代码,但仅支持一个条件变量
- JUC包实现的管程支持多个条件变量(例如ReentrantLock),但需要开发人员手动进行加锁和解锁操作
参考资料
All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.