公众号后台回复“ 学习 ”,获取作者独家秘制精品资料
扫描下方海报二维码,试听课程:
来源:
https://www.cnblogs.com/flashsun/p/11017431.html
最近在给别人讲解Java并发编程面试考点时,为了解释锁对象这个概念,想了一个形象的故事。
后来慢慢发现这个故事似乎能讲解Java并发编程中好多核心概念,于是完善起来形成了了这篇文章。
故事可能比较奇怪。有这么一个学校,里面有好多好多人,我们简单分成 学生 、 老师 、以及 宿管阿姨 。
学校中间还有一个很奇葩的水果超市,里面有个仓库放着 苹果 、 西瓜 、 橘子 。来这个超市的人,一方面可以拿走水果吃掉,另一方面也可以送来水果还钱。
不过超市还有一个很奇葩的规则,就是学生只能去吃或者送苹果,老师则只能西瓜,宿管阿姨只能橘子。
这个超市的进出也很有规矩,来这个超市的人,必须持有相应的 证件 ,学生则需要持有 学生证 ,老师需要持有 教师证 ,宿管阿姨需要持有 阿姨证 。
这三个证每个都分别只有一个,保管在超市门口的一个 领证处 ,人们进入这个超市之前,必须先去取证处那里领取相应的证件才能进入。
如果证件暂时被别人取走了拿不到,则需要到后方的 等待区 里面排队等证。
那这个等待区也有三个,分别是学生证等待区,教师证等待区,阿姨证等待区。
进入超市里面就更加奇葩了,不论是要从这个超市拿走水果,还是要送来水果,都需要通过一个 操作台 来控制,而这个操作台,同一时刻只能有一个人进行操作。
这个操作台为了防止有人霸占操作台过长时间,只允许一个人持续操作10s钟,10s之后会在屏幕上显示一个ID,只有这个ID的人才能来操作
至于选择什么号码,老师学生或是宿管阿姨都无法决定和干预,只能任凭这个操作台来决策。
但好在,每个人在操作台上都有自己的账号,操作一半被中断的数据并不会丢失。
这个故事的背景就介绍完了,下面这个学校就发生了各种各样的事。
首先我们假设,进这所学校的人,都是为了去超市做事情。某一时刻,操作台上显示了一个号码2号,这个号码通过各种学校大屏幕通知给所有的人。
于是ID为2号的学生小明看到了自己的号码,得知自己获得了进入超市操作控制台的 权利 ,于是出发前往超市。
小明首先到超市门口,问领证处的管理人员,“给我一张学生证!”。
管理人员找了找发现有一张学生证,于是便给了小明。小明拿到了学生证,顺利进入超市,并坐上了操作台前,登录了自己的账号系统。
小明此行的目的是为了拿走一个苹果,于是他点击了苹果商品的图标,系统显示苹果还有4个。于是小明顺利地拿走了苹果,系统将苹果数量-1,将新的苹果数量3记录到总系统库中。
接着小明走出超市,将学生证交还给了领证处,走出了校园,消失在外面的人海中。
接着操作台上显示了3号,同样通过学校大屏幕通知给了所有人。ID为3号的学生小张看到了自己的号码,得知自己获得了进入超市操作控制台的 权利 ,于是出发前往超市。
小张和小明做着完全相同的操作,但小张操作太慢了,刚刚点击完了苹果商品的图标,系统就显示了下一个人的号码5号。 此时小明只能被迫终止自己的操作,让出操作台的权利。
ID为5号的学生小王接到通知,兴冲冲地前往超市,并在领证处问管理人员,“给我一张学生证!”
管理人员找了找,发现学生证已经被小张取走了,只能告诉小王,“抱歉,学生证暂时没有,请到后面的学生证等待区排队吧!”。小王没办法,只能乖乖去排队了。
这时操作台再次显示了3号,也就是刚刚操作到一半的小张。小张此时还在超市里,并不需要重新进入,于是小张赶紧到操作台前继续着刚刚的操作,取走了一个苹果,离开了超市,交还了学生证。
此时领证处的管理人员收到了学生证,对着后面的学生证排队区喊,“学生证有啦,排队的人过来取吧!”
正在排队等证的5号小王听到后,从排队的队列里出来,准备领证并进入超市。但此时操作台上显示的号是另一个学生10号,10号学生拿走了学生证,进入超市开始操作。
操作到一半,操作台时间限制又到了,显示了小王的ID 5号。小王刚从等待领证的队列里出来,终于获得了进行下一步行动的准许,于是走向了领证处,“给我一张学生证!”
由于学生证已经被10号拿走,管理人员只能说,“抱歉,学生证暂时没有,请到后面的学生证等待区排队吧!”。
小王一看等了那么久居然又被别人抢先了一步,刚想爆粗口,想到了这个学校的名言,“这个世界是不公平的”,于是又乖乖走向了学生证等待区,继续排队。
等10号操作完出来了,还了学生证,小王又被领证处管理员喊话,“学生证有啦,排队的人过来取吧!”。
小王走出排队区,而此时操作台终于显示了小王的号码5号。小王这次顺利领取了学生证,进入了超市,坐在了操作台上,登录了自己的系统。
小王想买苹果,于是点击了苹果商品的按钮,但系统显示苹果数量为0!小王此时想了想,有了个接下来的计划:
继续呆在超市里,得空就去操作台上查询一下苹果的数量,直到有苹果为止。
但继续呆在超市里,可能导致想向超市送苹果的学生拿不到学生证,而自己也就永远无法得到苹果了,显然不妥。
所以小王的另一个想法是,走出超市,交还学生证,等下次有机会再进入超市查看苹果数量,直到有苹果为止。
这样虽然有机会得到苹果,但太累了,假如这期间根本没人往超市送苹果,那这一趟趟其实是白费事的。
于是小王想出了一个聪明的方案,我可以走出超市,到一个地方等待,在这里不会收到操作台的通知。
但如果有人向超市送苹果了,那这个等待区里会发一个信号,这时超市才有可能是有苹果的,这时我从等待区里出来,等待叫号的机会。
虽然苹果有可能被其他吃苹果的学生抢没,但这样起码不会浪费太多时间。
刚刚好超市旁边为每一种水果准备了好多等待区,一共有六个, 分别是:苹果没了等待区,西瓜没了等待区,橘子没了等待区。苹果满了等待区,西瓜满了等待区,橘子满了等待区。
小王很聪明,去了苹果没了等待区,等待着有人往里送苹果的信号。
这时小孙走进了超市,给超市添置了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) 。
这是操作台再次显示了3号,也就是刚刚操作到一半的小张。小张此时还在超市里 (不释放锁) ,并不需要重新进入,于是他赶紧到操作台前继续着刚刚的操作 (线程切换,继续执行中断的代码 ),取走了一个苹果,离开了超市,交还了学生证 (释放锁) 。
此时领证处的管理人员收到了学生证,对着后面的学生证排队区喊,“学生证有啦,排队的人过来取吧!” (通知同步队列出队) 。
正在排队等证的5号小王听到后,从排队的队列里出来,准备领证并进入超市。但此时操作台上显示的号是另一个学生10号,10号学生拿走了学生证,进入超市开始操作。
操作到一半,操作台时间限制又到了,显示了小王的ID 5号。小王刚从等待领证的队列里出来,终于获得了进行下一步行动的准许,于是走向了领证处,“给我一张学生证!”
由于学生证已经被10号拿走,管理人员只能说,“抱歉,学生证暂时没有,请到后面的学生证等待区排队吧!”
小王一看等了那么久居然又被别人抢先了一步,刚想爆粗口,想到了这个学校的名言,“这个世界是不公平的”,于是又乖乖走向了学生证等待区,继续排队。 (非公平锁,并不是谁等的时间最长谁就获取锁)
等10号操作完出来了,还了学生证,小王又被领证处管理员喊话,“学生证有啦,排队的人过来取吧!”
小王走出排队区,而此时操作台终于显示了小王的号码5号。小王这次顺利领取了学生证,进入了超市,坐在了操作台上,登录了自己的系统。
小王想买苹果,于是点击了苹果商品的按钮,但系统显示苹果数量为0!小王此时想了想,有了个接下来的计划:
继续呆在超市里,得空就去操作台上查询一下苹果的数量,直到有苹果为止。
但继续呆在超市里,可能导致想向超市送苹果的学生拿不到学生证,而自己也就永远无法得到苹果了,显然不妥。 (sychronized代码块里循环等待)
所以小王的另一个想法是,走出超市,交还学生证,等下次有机会再进入超市查看苹果数量,直到有苹果为止。
这样虽然有机会得到苹果,但太累了,假如这期间根本没人往超市送苹果,那这一趟趟其实是白费事的。 (sychronized代码块外循环等待)
于是小王想出了一个聪明的方案,我可以走出超市,到一个地方等待 (wait) ,在这里不会收到操作台的通知。 如果有人向超市送苹果了,那这个等待区里会发一个信号 (notify) ,这时超市才有可能是有苹果的,这时我从等待区里出来,等待叫号的机会。
虽然苹果有可能被其他吃苹果的学生抢没,但这样起码不会浪费太多时间。 (等待通知机制)
刚刚好超市旁边为每一种水果准备了好多等待区 (等待队列WaitQueue) ,一共有六个,分别是:苹果没了等待区,西瓜没了等待区,橘子没了等待区。苹果满了等待区,西瓜满了等待区,橘子满了等待区 (条件变量Condition) 。
小王很聪明,走出超市交还学生证 (wait会释放锁) ,去了苹果没了等待区(wait),等待着有人往里送苹果的信号 (同步信号-唤醒) 。
这时小孙走进了超市,给超市添置了5个苹果,并换来了零花钱。之后他立刻通知苹果没了等待区,给了个信号“超市有苹果啦! (AppleNotEmpty.notifyAll) ”,但此时小孙还没有走出超市呢 (notify不释放锁) 。
小王在等待区里收到信号,立刻走出了等待区,等待被叫号,以完成自己吃苹果的任务。但很不幸,在小王得到叫号机会之前,苹果又被其他几个学生抢光了,这时才轮到小王。
小王也很聪明,他考虑到了这种情况,没有直接取苹果,而是重新查询了一变苹果数量 (wait一般配合while条件) ,发现苹果数量为0,于是重复之前的步骤,小王再次回到了苹果没了等待区。
接下来的时间里,小王不断在苹果没了等待区和学生证等待区移动,小王发现为了吃一个苹果太难了,必须同时满足,苹果没了等待区发来了“超市有苹果了”的信号,领证区此时有学生证,并且在操作台上查询出的苹果数量不为0。
终于有一次。小王成功满足了这三个条件,在操作台上看到苹果的数量为1!小王正激动地准备按下购买按钮,可此时操作台一闪,突然出现了别人的号码。
这个人是超市管理员,拿着一张特殊的超市管理员证顺利进入了超市,将苹果拿走,此时苹果数量又变成了0。
之后又轮到小王操作,但小王并不知道之前发生的一切,他眼中明明看到苹果数量是1。小王为了保险起见,又多次查询了苹果数量,发现仍然是1 (非volatile修饰的变量不保证线程之间的可见性) ,于是兴奋地点下了购买按钮!
于是,操作台对根本没有苹果的储藏区发出了取苹果的指令,该系统根本没有想到会有这种事情发生,于是机器炸了,小王牺牲 (抛出运行时异常,线程释放锁并终止) 。
数年后,之前做操作台的人已经被枪毙了,学校又高薪聘请了一位高人来建造,解决了之前的那个问题 (volatile) 。
超市又顺利运转起来,有时超市只有一个人 (不同线程进入锁对象相同的临界区会互斥,只有一个线程可以进入) ,有时超市会有三个人 (不同锁对象的临界区不互斥) ,分别是学生、老师、宿管阿姨,他们仨人互不影响,相安无事。学校的生活再次丰富了起来。
故事讲完了,虽然不能解释全部并发编程的内容,也不能处处都很恰当地说明细节,但却是一个很有趣的思考过程
希望大家也能积极讨论下故事中的错误和不完善的地方,一起将故事讲的更好。下面整理一下故事中出现的东西和寓意。
END
如有收获,请划至底部,点击“ 在看 ”,谢谢!
欢迎长按下图关注公众号 石杉的架构笔记
BAT架构经验倾囊相授