- 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
- 管程模型与面向对象高度契合
同步
- 在管程模型里,共享变量和对共享变量的操作是被封装起来的,最外层的框是代表封装的意思
- 框的上面只有一个入口,并且在入口旁边还有一个 入口等待队列
- 当多个线程同时试图进入管程内部时,只允许一个线程进入,其他线程就在 入口等待队列 中等待
- 管程里还引入了 条件变量 的概念, 每个条件变量都对应一个等待队列
- 条件变量和其对应等待队列的作用: 线程同步
实例:出队入队
// 下列三对操作的语义是相同的 // Condition.await() Object.wait() // Condition.signal() Object.notify() // Condition.signalAll() Object.notifyAll() public class BlockedQueue<T> { private static final int MAX_SIZE = 10; // 可重入锁 private final Lock lock = new ReentrantLock(); // 条件变量:队列不满 private final Condition notFull = lock.newCondition(); // 条件变量:队列不空 private final Condition notEmpty = lock.newCondition(); // 队列实际存储:栈 private final Stack<T> stack = new Stack<>(); // 入队 public void enq(T t) { // 先获得互斥锁,类似于管程中的入口 lock.lock(); try { while (stack.size() >= MAX_SIZE) { // 队列已满,等待队列不满,才可入队 notFull.await(); } // 入队后,通知队列不空,可出队 stack.push(t); notEmpty.signalAll(); } catch (InterruptedException ignored) { } finally { lock.unlock(); } } // 出队 public T deq() { // 先获得互斥锁,类似于管程中的入口 lock.lock(); try { while (stack.isEmpty()) { // 队列已空,等待队列不空,才可出队 notEmpty.await(); } // 出队后,通知队列不满,可入队 T pop = stack.pop(); notFull.signalAll(); return pop; } catch (InterruptedException ignored) { } finally { lock.unlock(); } return null; } }
- 假设线程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),但需要开发人员手动进行加锁和解锁操作
转载请注明出处:http://zhongmingmao.me/2019/04/27/java-concurrent-monitor/
访问原文「Java并发 -- 管程」获取最佳阅读体验并参与讨论
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Java并发系列—并发编程基础
- [Java并发-17-并发设计模式] Immutability模式:如何利用不变性解决并发问题?
- JAVA并发编程之并发模拟工具
- Java并发系列—并发编程的挑战
- Core Java 并发:理解并发概念
- [Java并发-11] 并发容器的使用
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Tomcat与Java Web开发技术详解
孙卫琴 / 电子工业出版社 / 2004-4-1 / 45.00元
《Tomcat与Java Web开发技术详解》编辑推荐:Jakarta Tomcat服务器是在SUN公司的JSWDK(JavaServer Web DevelopmentKit,SUN公司推出的小型Servlet/JSP调试工具)的基础上发展起来的一个优秀的Java Web应用容器,它是Apache-Jakarta的一个子项目。Tomcat被JavaWorld杂志的编辑选为2001年度最具创新的J......一起来看看 《Tomcat与Java Web开发技术详解》 这本书的介绍吧!