点击上方蓝色“程序猿DD”,选择“设为星标”

回复“资源”获取独家整理的学习资料!

作者 | 闪客sun

来源 | cnblogs.com/flashsun/p/11017431.html

最近在给别人讲解Java并发编程面试考点时,为了解释锁对象这个概念,想了一个形象的故事。后来慢慢发现这个故事似乎能讲解Java并发编程中好多核心概念,于是完善起来形成了了这篇文章。大家先忘记并发编程,只听我给你讲个故事。

故事可能比较奇怪。有这么一个学校,里面有好多好多人,我们简单分成学生老师、以及宿管阿姨。学校中间还有一个很奇葩的水果超市,里面有个仓库放着苹果西瓜橘子。来这个超市的人,一方面可以拿走水果吃掉,另一方面也可以送来水果还钱。不过超市还有一个很奇葩的规则,就是学生只能去吃或者送苹果,老师则只能西瓜,宿管阿姨只能橘子。

这个超市的进出也很有规矩,来这个超市的人,必须持有相应的证件,学生则需要持有学生证,老师需要持有教师证,宿管阿姨需要持有阿姨证。这三个证每个都分别只有一个,保管在超市门口的一个领证处,人们进入这个超市之前,必须先去取证处那里领取相应的证件才能进入。如果证件暂时被别人取走了拿不到,则需要到后方的等待区里面排队等证。那这个等待区也有三个,分别是学生证等待区,教师证等待区,阿姨证等待区。

进入超市里面就更加奇葩了,不论是要从这个超市拿走水果,还是要送来水果,都需要通过一个操作台来控制,而这个操作台,同一时刻只能有一个人进行操作。这个操作台为了防止有人霸占操作台过长时间,只允许一个人持续操作10s钟,10s之后会在屏幕上显示一个ID,只有这个ID的人才能来操作,至于选择什么号码,老师学生或是宿管阿姨都无法决定和干预,只能任凭这个操作台来决策。但好在,每个人在操作台上都有自己的账号,操作一半被中断的数据并不会丢失。

这个故事的背景就介绍完了,下面这个学校就发生了各种各样的事。

首先我们假设,进这所学校的人,都是为了去超市做事情。某一时刻,操作台上显示了一个号码2号,这个号码通过各种学校大屏幕通知给所有的人。于是ID为2号的学生小明看到了自己的号码,得知自己获得了进入超市操作控制台的权利,于是出发前往超市。小明首先到超市门口,问领证处的管理人员,“给我一张学生证!”。管理人员找了找发现有一张学生证,于是便给了小明。小明拿到了学生证,顺利进入超市,并坐上了操作台前,登录了自己的账号系统。小明此行的目的是为了拿走一个苹果,于是他点击了苹果商品的图标,系统显示苹果还有4个。于是小明顺利地拿走了苹果,系统将苹果数量-1,将新的苹果数量3记录到总系统库中。接着小明走出超市,将学生证交还给了领证处,走出了校园,消失在外面的人海中。

接着操作台上显示了3号,同样通过学校大屏幕通知给了所有人。ID为3号的学生小张看到了自己的号码,得知自己获得了进入超市操作控制台的权利,于是出发前往超市。小张和小明做着完全相同的操作,但小张操作太慢了,刚刚点击完了苹果商品的图标,系统就显示了下一个人的号码5号。此时小明只能被迫终止自己的操作,让出操作台的权利。ID为5号的学生小王接到通知,兴冲冲地前往超市,并在领证处问管理人员,“给我一张学生证!”。管理人员找了找,发现学生证已经被小明取走了,只能告诉小王,“抱歉,学生证暂时没有,请到后面的学生证等待区排队吧!”。小王没办法,只能乖乖去排队了。

这是操作台再次显示了2号,也就是刚刚操作到一半的小明。小明此时还在超市里,并不需要重新进入,于是小明赶紧到操作台前继续着刚刚的操作,取走了一个苹果,离开了超市,交还了学生证。此时领证处的管理人员收到了学生证,对着后面的学生证排队区喊,“学生证有啦,排队的人过来取吧!”。正在排队等证的5号小王听到后,从排队的队列里出来,准备领证并进入超市。但此时操作台上显示的号是另一个学生10号,10号学生拿走了学生证,进入超市开始操作。操作到一半,操作台时间限制又到了,显示了小王的ID5号。小王刚从等待领证的队列里出来,终于获得了进行下一步行动的准许,于是走向了领证处,“给我一张学生证!”,由于学生证已经被10号拿走,管理人员只能说,“抱歉,学生证暂时没有,请到后面的学生证等待区排队吧!”。小王一看等了那么久居然又被别人抢先了一步,刚想爆粗口,想到了这个学校的名言,“这个世界是不公平的”,于是又乖乖走向了学生证等待区,继续排队。

等10号操作完出来了,还了学生证,小王又被领证处管理员喊话,“学生证有啦,排队的人过来取吧!”。小王走出排队区,而此时操作台终于显示了小王的号码5号。小王这次顺利领取了学生证,进入了超市,坐在了操作台上,登录了自己的系统。小王想买苹果,于是点击了苹果商品的按钮,但系统显示苹果数量为0!小王此时想了想,有了个接下来的计划:

  1. 继续呆在超市里,得空就去操作台上查询一下苹果的数量,直到有苹果为止。但继续呆在超市里,可能导致想向超市送苹果的学生拿不到学生证,而自己也就永远无法得到苹果了,显然不妥。

  2. 所以小王的另一个想法是,走出超市,交还学生证,等下次有机会再进入超市查看苹果数量,直到有苹果为止。这样虽然有机会得到苹果,但太累了,假如这期间根本没人往超市送苹果,那这一趟趟其实是白费事的。

  3. 于是小王想出了一个聪明的方案,我可以走出超市,到一个地方等待,在这里不会收到操作台的通知。如果有人向超市送苹果了,那这个等待区里会发一个信号,这时超市才有可能是有苹果的,这时我从等待区里出来,等待叫号的机会。虽然苹果有可能被其他吃苹果的学生抢没,但这样起码不会浪费太多时间。

刚刚好超市旁边为每一种水果准备了好多等待区,一共有六个,分别是:苹果没了等待区,西瓜没了等待区,橘子没了等待区。苹果满了等待区,西瓜满了等待区,橘子满了等待区。小王很聪明,去了苹果没了等待区,等待着有人往里送苹果的信号。

这时小孙走进了超市,给超市添置了5个苹果,并换来了零花钱。之后他立刻通知苹果没了等待区,给了个信号“超市有苹果啦!”,但此时小孙还没有走出超市呢。小王在等待区里收到信号,立刻走出了等待区,等待被叫号,以完成自己吃苹果的任务。但很不幸,在小王得到叫号机会之前,苹果又被其他几个学生抢光了,这时才轮到小王。小王也很聪明,他考虑到了这种情况,没有直接取苹果,而是重新查询了一变苹果数量,发现苹果数量为0,于是重复之前的步骤,小王再次回到了苹果没了等待区。

接下来的时间里,小王不断在苹果没了等待区和学生证等待区移动,小王发现为了吃一个苹果太难了,必须同时满足,苹果没了等待区发来了“超市有苹果了”的信号,领证区此时有学生证,并且在操作台上查询出的苹果数量不为0。终于有一次。小王成功满足了这三个条件,在操作台上看到苹果的数量为1!小王正激动地准备按下购买按钮,可此时操作台一闪,突然出现了别人的号码。这个人是超市管理员,拿着一张特殊的超市管理员证顺利进入了超市,将苹果拿走,此时苹果数量又变成了0。之后又轮到小王操作,但小王并不知道之前发生的一切,他眼中明明看到苹果数量是1。小王为了保险起见,又多次查询了苹果数量,发现仍然是1,于是兴奋地点下了购买按钮!于是,操作台对根本没有苹果的储藏区发出了取苹果的指令,该系统根本没有想到会有这种事情发生,于是机器炸了,整个学校夷为平地。

数年后,学校慢慢被重新建立了起来,之前做操作台的人已经被枪毙了,高薪聘请了一位高人来建造,解决了之前的那个问题。超市又顺利运转起来,有时超市只有一个人,有时超市会有三个人,分别是学生、老师、宿管阿姨,他们仨人互不影响,相安无事。学校的生活再次丰富了起来。

---------华丽的分割线---------

这个故事包含了Java多线程的大部分核心问题,下面我把故事重新讲一遍。

有这么一个学校(Java虚拟机),里面有好多好多(线程),我们简单分成学生老师、以及宿管阿姨。学校中间还有一个很奇葩的水果超市(临界区),里面有个仓库放着苹果西瓜橘子(临界区里的受保护资源)。来这个超市的人,一方面可以拿走水果吃掉,另一方面也可以送来水果还钱。不过超市还有一个很奇葩的规则,就是学生只能去吃或者送苹果,老师则只能西瓜,宿管阿姨只能橘子。

这个超市的进出也很有规矩,来这个超市的人,必须持有相应的证件(锁对象),学生则需要持有学生证,老师需要持有教师证,宿管阿姨需要持有阿姨证(不同的锁对象)。这三个证每个都分别只有一个,保管在超市门口的一个领证处(获取锁的地方--可以说是堆吧),人们进入这个超市之前,必须先去取证处那里领取相应的证件(获取锁)才能进入。如果证件暂时被别人取走了拿不到(获取锁失败),则需要到后方的等待区(同步队列SychronizedQueue)里面排队等证。那这个等待区也有三个,分别是学生证等待区,教师证等待区,阿姨证等待区(每个锁对象对应一个同步队列)。

进入超市里面就更加奇葩了,不论是要从这个超市拿走水果,还是要送来水果,都需要通过一个操作台(单核CPU)来控制,而这个操作台,同一时刻只能有一个人进行操作。这个操作台为了防止有人霸占操作台过长时间,只允许一个人持续操作10s钟(CPU时间片),10s之后会在屏幕上显示一个ID,只有这个ID的人才能来操作(线程切换)。至于选择什么号码,老师学生或是宿管阿姨都无法决定和干预,只能任凭这个操作台来决策(操作系统决定线程的切换和时间的分配)。但好在,每个人在操作台上都有自己的账号(线程的工作内存),操作一半被中断的数据并不会丢失。

这个故事的背景就介绍完了,下面这个学校就发生了各种各样的事。

首先我们假设,进这所学校的人,都是为了去超市做事情。首先人出现在学校外(线程状态NEW),人进入学校(线程状态RUNNABLE)。某一时刻,操作台上显示了一个号码2号,这个号码通过各种学校大屏幕通知给所有的人。于是ID为2号的学生小明看到了自己的号码,得知自己获得了进入超市操作控制台的权利(获得CPU执行权),于是出发前往超市。小明首先到超市门口,问领证处的管理人员,“给我一张学生证!”(获取锁)。管理人员找了找发现有一张学生证,于是便给了小明。小明拿到了学生证,顺利进入超市(获取锁成功,进入临界区),并坐上了操作台前,登录了自己的账号系统(准备好工作内存,开始执行临界区代码)。小明此行的目的是为了拿走一个苹果,于是他点击了苹果商品的图标,系统显示苹果还有4个。于是小明顺利地拿走了苹果,系统将苹果数量-1,将新的苹果数量3记录到总系统库中(代码)。接着小明走出超市(代码执行完毕出临界区),将学生证交还给了领证处(释放锁),走出了校园(线程状态TERMINAL),消失在外面的人海中。

接着操作台上显示了3号,同样通过学校大屏幕通知给了所有人。ID为3号的学生小张看到了自己的号码,得知自己获得了进入超市操作控制台的权利,于是出发前往超市。小张和小明做着完全相同的操作,但小张操作太慢了,刚刚点击完了苹果商品的图标,系统就显示了下一个人的号码5号。此时小明只能被迫终止自己的操作,让出操作台的权利(线程切换)。ID为5号的学生小王接到通知,兴冲冲地前往超市,并在领证处问管理人员,“给我一张学生证!”。管理人员找了找,发现学生证已经被小明取走了,只能告诉小王,“抱歉,学生证暂时没有,请到后面的学生证等待区(同步队列WaitQueue)排队吧!”(获取锁失败)。小王没办法,只能乖乖去排队了(线程状态BLOCKING)。

这是操作台再次显示了2号,也就是刚刚操作到一半的小明。小明此时还在超市里(不释放锁),并不需要重新进入,于是小明赶紧到操作台前继续着刚刚的操作(线程切换,继续执行中断的代码),取走了一个苹果,离开了超市,交还了学生证(释放锁)。此时领证处的管理人员收到了学生证,对着后面的学生证排队区喊,“学生证有啦,排队的人过来取吧!”(通知同步队列出队)。正在排队等证的5号小王听到后,从排队的队列里出来,准备领证并进入超市。但此时操作台上显示的号是另一个学生10号,10号学生拿走了学生证,进入超市开始操作。操作到一半,操作台时间限制又到了,显示了小王的ID5号。小王刚从等待领证的队列里出来,终于获得了进行下一步行动的准许,于是走向了领证处,“给我一张学生证!”,由于学生证已经被10号拿走,管理人员只能说,“抱歉,学生证暂时没有,请到后面的学生证等待区排队吧!”。小王一看等了那么久居然又被别人抢先了一步,刚想爆粗口,想到了这个学校的名言,“这个世界是不公平的”,于是又乖乖走向了学生证等待区,继续排队。(非公平锁,并不是谁等的时间最长谁就获取锁)

等10号操作完出来了,还了学生证,小王又被领证处管理员喊话,“学生证有啦,排队的人过来取吧!”。小王走出排队区,而此时操作台终于显示了小王的号码5号。小王这次顺利领取了学生证,进入了超市,坐在了操作台上,登录了自己的系统。小王想买苹果,于是点击了苹果商品的按钮,但系统显示苹果数量为0!小王此时想了想,有了个接下来的计划:

  1. 继续呆在超市里,得空就去操作台上查询一下苹果的数量,直到有苹果为止。但继续呆在超市里,可能导致想向超市送苹果的学生拿不到学生证,而自己也就永远无法得到苹果了,显然不妥。(sychronized代码块里循环等待)

  2. 所以小王的另一个想法是,走出超市,交还学生证,等下次有机会再进入超市查看苹果数量,直到有苹果为止。这样虽然有机会得到苹果,但太累了,假如这期间根本没人往超市送苹果,那这一趟趟其实是白费事的。(sychronized代码块外循环等待)

  3. 于是小王想出了一个聪明的方案,我可以走出超市,到一个地方等待(wait),在这里不会收到操作台的通知。如果有人向超市送苹果了,那这个等待区里会发一个信号(notify),这时超市才有可能是有苹果的,这时我从等待区里出来,等待叫号的机会。虽然苹果有可能被其他吃苹果的学生抢没,但这样起码不会浪费太多时间。(等待通知机制)

刚刚好超市旁边为每一种水果准备了好多等待区(等待队列WaitQueue),一共有六个,分别是:苹果没了等待区,西瓜没了等待区,橘子没了等待区。苹果满了等待区,西瓜满了等待区,橘子满了等待区(条件变量Condition)。小王很聪明,走出超市交还学生证(wait会释放锁),去了苹果没了等待区(wait),等待着有人往里送苹果的信号(同步信号-唤醒)。

这时小孙走进了超市,给超市添置了5个苹果,并换来了零花钱。之后他立刻通知苹果没了等待区,给了个信号“超市有苹果啦!(AppleNotEmpty.notifyAll)”,但此时小孙还没有走出超市呢(notify不释放锁)。小王在等待区里收到信号,立刻走出了等待区,等待被叫号,以完成自己吃苹果的任务。但很不幸,在小王得到叫号机会之前,苹果又被其他几个学生抢光了,这时才轮到小王。小王也很聪明,他考虑到了这种情况,没有直接取苹果,而是重新查询了一变苹果数量(wait一般配合while条件),发现苹果数量为0,于是重复之前的步骤,小王再次回到了苹果没了等待区。

接下来的时间里,小王不断在苹果没了等待区和学生证等待区移动,小王发现为了吃一个苹果太难了,必须同时满足,苹果没了等待区发来了“超市有苹果了”的信号,领证区此时有学生证,并且在操作台上查询出的苹果数量不为0。终于有一次。小王成功满足了这三个条件,在操作台上看到苹果的数量为1!小王正激动地准备按下购买按钮,可此时操作台一闪,突然出现了别人的号码。这个人是超市管理员,拿着一张特殊的超市管理员证顺利进入了超市,将苹果拿走,此时苹果数量又变成了0。之后又轮到小王操作,但小王并不知道之前发生的一切,他眼中明明看到苹果数量是1。小王为了保险起见,又多次查询了苹果数量,发现仍然是1(非volatile修饰的变量不保证线程之间的可见性),于是兴奋地点下了购买按钮!于是,操作台对根本没有苹果的储藏区发出了取苹果的指令,该系统根本没有想到会有这种事情发生,于是机器炸了,小王牺牲(抛出运行时异常,线程释放锁并终止)。

数年后,之前做操作台的人已经被枪毙了,学校又高薪聘请了一位高人来建造,解决了之前的那个问题(volatile)。超市又顺利运转起来,有时超市只有一个人(不同线程进入锁对象相同的临界区会互斥,只有一个线程可以进入),有时超市会有三个人(不同锁对象的临界区不互斥),分别是学生、老师、宿管阿姨,他们仨人互不影响,相安无事。学校的生活再次丰富了起来。

故事讲完了,虽然不能解释全部并发编程的内容,也不能处处都很恰当地说明细节,但确是一个很有趣的思考过程,希望大家也能积极讨论下故事中的错误和不完善的地方,一起将故事讲的更好。下面整理一下故事中出现的东西和寓意。

东西 寓意
线程
通行证 锁对象
水果超市 临界区代码
水果 受保护资源
操作台 CPU
叫号 时间片分配
领证处 获取锁
等待区 等待队列
领证排队区 同步队列
水果储藏区 主内存
每个人的账号系统 工作内存

本文通过OpenWrite的Markdown转换工具发布

关注我,回复“加群”加入各种主题讨论群

  • IntelliJ IDEA 2019.3发布,诟病的2019.2版本终成过去式

  • Spring Cloud Hoxton发布,Spring Boot 2.2.x不再孤单

  • 聊聊前后端分离接口规范

  • Token ,Cookie、Session傻傻分不清楚?

  • 使用 LocalDateTime 而不是 Date

朕已阅 

忘掉什么鬼并发,先听完这个故事!相关推荐

  1. 忘掉 Java 并发,先听完这个故事。。。

    最近在给别人讲解 Java 并发编程面试考点时,为了解释锁对象这个概念,想了一个形象的故事. 后来慢慢发现这个故事似乎能讲解 Java 并发编程中好多核心概念,于是完善起来形成了这篇文章. 大家先忘记 ...

  2. 忘掉Java并发,先听完这个故事...

    点击上方"朱小厮的博客",选择"设为星标" 回复"666"获取公众号专属资料 来源:http://uee.me/b27Xt 最近在给别人讲解 ...

  3. 听完这个故事,彻底搞懂 Java 并发!

    最近在给别人讲解 Java 并发编程面试考点时,为了解释锁对象这个概念,想了一个形象的故事. 后来慢慢发现这个故事似乎能讲解 Java 并发编程中好多核心概念,于是完善起来形成了这篇文章. 大家先忘记 ...

  4. 开源究竟有什么魅力?听完这 4 个故事你也许会明白

    零 导语 如今,「开源」在国内外已不是一个新名词,随着数字化转型.云原生.各路玩家的加入,开源生态日新月异. 除了早年已建立起庞大社区和丰富生态的 Docker.Nginx.Kubernetes 等开 ...

  5. 看完这个故事,你就知道程序员为什么选公司就要去上升期的

    看完这个故事,你就知道程序员为什么选公司就要去上升期的 https://www.toutiao.com/i6948390604984402462/?tt_from=weixin&utm_cam ...

  6. 20130327-[转]讲完这个故事我就要嫁给别人

    讲完这个故事我就要嫁给别人 内容来源:中国时尚品牌网 [2013年03月06日07:41] 这个故事没有惊心动魄,只有波澜不惊. 讲完,我便将你安放于心. 从小就是个小心翼翼的姑娘,特别害怕将自己置于 ...

  7. 为什么计算机模拟不了人脑,为什么计算机永远不会拥有人类的意识?听完科学家的解释恍然大悟...

    原标题:为什么计算机永远不会拥有人类的意识?听完科学家的解释恍然大悟 人类是现在地球的霸主,是智慧生命.地球在6500万年前也有一类霸主生物,它们就是恐龙.可是恐龙尽管称霸地球1.6亿年的岁月,但是它 ...

  8. 【完结】听完这12次分享,你应该完成了AI小白的蜕变

    文章首发于微信公众号<有三AI> [完结]听完这12次分享,你应该完成了AI小白的蜕变 专栏<AI白身境>正式完结了,在这一个专题中,我们给大家从Linux的基本知识,讲到了深 ...

  9. 端午回家,听完你是做程序员,你家里人是什么反应?

    端午回家,听完你做程序员,家里人什么反应? ▽▽▽ 做安卓底层? 那你们这个要做几年才能做到中上层? ●●● 打英语,偶尔打英文, 一天一两百行,有时候十几行, 一行大概三四块, 每天写得越多越不值钱 ...

最新文章

  1. 使用metasploit中Evasion模块
  2. 【JavaSE04】Java中循环语句for,while,do···while-思维导图
  3. 详解const和#define
  4. 云监控Agent指南-Linux版
  5. 教学思路C#之入门一 认识简单的C#结构
  6. [蓝桥杯][2017年第八届真题]拉马车(String)
  7. 浅谈项目管理中的四要素
  8. js实现同时提交多个表单
  9. html文件内容搜索,html读出文本文件内容
  10. mysql 获取年预提,【判断题】正确核算待摊费用和预提费用,有助于划分本期费用与非本期费用的界限。...
  11. RTX5 | STM32H743+CubeMX+RTX5+两路FDCAN驱动+CANopen协议
  12. 有助于获得优质流量的免费SEO关键词工具
  13. 用C#打开文件对话框的方法和简单使用的程序
  14. vue-html5-editor接收数据,在vue中获取wangeditor的html和text的操作
  15. matplotlib实现数据的可视化
  16. QT调用OpenCV
  17. vscode 下载加速方法
  18. Visual studio 无法打开源文件的问题或系统找不到指定文件
  19. EXTJS记事本:当CompositeField遇上RowEditor
  20. gitter 卸载_最佳Gitter频道:转到

热门文章

  1. linux suse 软件管理工具 zypper 简介
  2. linux 桌面管理器 xfce 用户自动登录
  3. golang panic和recover 捕获异常
  4. python3 pyquery模块 解析html网页
  5. linux挂载卸载不掉 umount target is busy
  6. shell 数组排序
  7. KVM-Qemu-Libvirt三者之间的关系
  8. CentOS 6.4下安装中文输入法
  9. java 课程设计 计算器_JAVA课程设计-计算器(201521123028 李家俊)
  10. golang runtime.findrunnable epoll_wait lock 占用CPU 过多排查