亲宝软件园·资讯

展开

超强图文|并发编程【等待/通知机制】就是这个feel~

日拱一兵 人气:0
![](https://img2020.cnblogs.com/other/1583165/202003/1583165-20200317074941613-1489223916.png) > - 你有一个思想,我有一个思想,我们交换后,一个人就有两个思想 > > - If you can NOT explain it simply, you do NOT understand it well enough 现陆续将Demo代码和技术文章整理在一起 [Github实践精选](https://github.com/FraserYu/learnings) ,方便大家阅读查看,本文同样收录在此,觉得不错,还请Star ![](https://img2020.cnblogs.com/other/1583165/202003/1583165-20200317074943050-752187530.png) ## 并发编程为什么会有等待通知机制 上一篇文章说明了 [Java并发死锁解决思路](https:/https://img.qb5200.com/download-x/dayarch.top/p/java-concurrency-dead-lock.html) , 解决死锁的思路之一就是 `破坏请求和保持条件`, 所有柜员都要通过**唯一**的账本管理员一次性拿到所有转账业务需要的账本,就像下面这样: ![](https://img2020.cnblogs.com/other/1583165/202003/1583165-20200317074943496-1802163601.png) 没有等待/通知机制之前,所有柜员都通过死循环的方式不断向账本管理员申请所有账本,程序的体现就是这样: ```java while(!accountBookManager.getAllRequiredAccountBook(this, target)) ; ``` 假如账本管理员是年轻小伙,腿脚利落(即执行 getAllRequiredAccountBook方法耗时短),并且多个柜员转账的业务冲突量不大,这个方案简单粗暴且有效,柜员只需要尝试几次就可以成功(即通过少量的循环可以实现) 过了好多年,年轻的账本管理员变成了年迈的老人,行动迟缓(即执行 getAllRequiredAccountBook 耗时长),同时,多个柜员转账的业务冲突量也变大,之前几十次循环能做到的,现在可能就要申请成千上百,甚至上万次才能完成一次转账 ![](https://img2020.cnblogs.com/other/1583165/202003/1583165-20200317074944062-1756663620.png) **人工无限申请浪费口舌, 程序无限申请浪费CPU。聪明的人就想到了 `等待/通知` 机制** ## 等待/通知机制 无限循环实在太浪费CPU,而理想情况应该是这样: - 柜员A如果拿不到所有账本,就傲娇的不再继续问了(线程阻塞自己 wait) - 柜员B归还了柜员A需要的账本之后就主动通知柜员A账本可用(通知等待的线程 notify/notifyAll) 做到这样,就能避免循环等待消耗CPU的问题了 --- 现实中有太多场景都在应用等待/通知机制。欢迎观临红浪漫,比如去XX办证,去医院就医/体检。 下面请自行脑补一下去医院就医或体检的画面, 整体流程类似这样: | 序号 | 就医 | 程序解释(自己的视角) | | ---- | :--------------------------------------------------- | ------------------------------------------------ | | 1 | 挂号成功,到诊室门口排号候诊 | 排号的患者(线程)尝试获取【互斥锁】 | | 2 | 大夫叫到自己,进入诊室就诊 | 自己【获取到互斥锁】 | | 3 | 大夫简单询问,要求做检查(患者缺乏报告不能诊断病因) | 进行【条件判断】,线程要求的条件【没满足】 | | 4 | 自己出去做检查 | 线程【主动释放】持有的互斥锁 | | 5 | 大夫叫下一位患者 | 另一位患者(线程)获取到互斥锁 | | 6 | 自己拿到检测报告 | 线程【曾经】要求的条件得到满足(实则【被通知】) | | 7 | 再次在诊室门口排号候诊 | 再次尝试获取互斥锁 | | 8 | ... | ... | 在【程序解释】一列,我将关键字(排队、锁、等待、释放....)已经用 `【】` 框了起来。Java 语言中,其内置的关键字 `synchronized` 和 方法`wait(),notify()/notifyAll()` 就能实现上面提到的等待/通知机制,我们将这几个关键字实现流程现形象化的表示一下: ![等待队列图](https://img2020.cnblogs.com/other/1583165/202003/1583165-20200317074945404-535856850.png) 这可不是一个简单的图,下面还要围绕这个图做很多文章,不过这里我必须要插播几个面试基础知识点了: 1. 一个锁对应一个【入口等待队列】,不同锁的入口等待队列没任何关系,说白了他们就不存在竞争关系。你想呀,不同患者进入眼科和耳鼻喉科看大夫一点冲突都没有 2. `wait(), notify()/notifyAll()` 要在 synchronized 内部被使用,并且,如果锁的对象是this,就要 `this.wait(),this.notify()/this.notifyAll()` , 否则JVM就会抛出 `java.lang.IllegalMonitorStateException` 的。你想呀,等待/通知机制就是从【竞争】环境逐渐衍生出来的策略,不在锁竞争内部使用或等待/通知错了对象, 自然是不符合常理的 ![](https://img2020.cnblogs.com/other/1583165/202003/1583165-20200317074945949-1712471555.png) 有了上面知识的铺垫,要想将无限循环策略改为等待通知策略,你还需要问自己四个问题: ### 灵魂 4 问 ![](https://img2020.cnblogs.com/other/1583165/202003/1583165-20200317074946769-76129469.png) 我们拿钱庄账本管理员的例子依依做以上回答: ![](https://img2020.cnblogs.com/other/1583165/202003/1583165-20200317074948385-92719355.png) 我们优化钱庄转账的程序: ```java public class AccountBookManager { List

加载全部内容

相关教程
猜你喜欢
用户评论