当前位置: 首页 > 科技观察

Java并发算了,先听听这个故事吧……

时间:2023-03-13 17:54:04 科技观察

最近在给别人讲解Java并发编程面试考点的时候,为了解释锁对象的概念,想到了一个生动的故事。图片来自Pexels。后来慢慢发现,这个故事好像解释了很多Java并发编程中的核心概念,于是完善了一下,形成了这篇文章。大家先把并发编程忘掉,听我给大家讲个故事:故事可能比较奇怪,有这么一所学校,里面有很多人,我们简单的分为学生、老师、宿舍阿姨。学校中间还有一个非常棒的水果超市,里面有一个仓库,里面有苹果、西瓜、橘子。来这家超市的人,一方面可以把水果带走吃掉,另一方面可以送水果还钱。不过,超市里还有一个很奇怪的规定,就是学生只能吃或者送苹果,老师只能吃或者送西瓜,宿舍阿姨只能吃或者送橘子。这家超市的进出也很有规律。来这家超市的人必须持有相应的证件,学生需要持有学生证,老师需要持有教师证,宿舍阿姨需要持有阿姨证。这三张证件各只有一张,存放在超市门口的证件领取处。进入超市前,人们必须先到取证处领取相应证件后方可进入。如果证件临时被他人拿走无法领取,则需要在后面的等候区排队等候证件。这个等候区还有三个等候区,分别是学生证等候区,教师证等候区,阿姨证等候区。进入超市,更是陌生。无论是从这家超市取水果还是送水果,都需要通过一个操作台进行控制,而这个操作台一次只能由一个人操作。为了防止有人长时间占用控制台,只允许一个人连续操作10s。10s后,屏幕上会显示一个ID,只有拥有这个ID的人才能操作。至于选择哪个号码,无论是老师、学生还是宿舍阿姨都无法决定和干预,只能让控制台来决定。不过好在每个人在控制台都有自己的账号,操作到一半就中断的数据不会丢失。这个故事的背景讲完了,下面这所学校里发生了各种各样的事情。首先我们假设进入这个学校的人都是去超市办事的。某一刻,控制台上出现了一个数字2,这个数字通过各个学校的大屏幕通知给了所有人。于是,2号学生小明看到了自己的号码,得知自己获得了进入超市操作台的权限,便出发前往超市。小明先是走到超市门口,对领卡处的管理人员说:“给我一张学生卡!”。管理人员找了找,找到一张学生证,就给了小明。小明拿到了学生证,顺利的进入了超市,坐在操作台前,登陆了自己的账号系统。小明此行的目的是带走一个苹果,于是点击苹果产品图标,系统显示还有4个苹果。于是小明成功拿走了苹果,系统记录了苹果个数-1,并在系统总库中记录了新的苹果个数3。随后小明走出超市,将学生证还给办证处,走出校园,消失在外面的人群中。然后3号就显示在了控制台上,也通过学校的大屏幕通知了大家。3号学生小张看到自己的号码,得知自己获得了进入超市操作台的权限,便出发前往超市。小张和小明在做着一模一样的操作,只是小张的操作太慢了。刚点了一个苹果产品的图标,系统就显示下一个人的数字5。此时,小明只能被迫终止运营,放弃控制台操作权。5号学生小王接到通知,兴冲冲地来到超市,向领证处的管理人员询问:“给我一张学生证!”管理人员四处寻找,发现学生证被张某拿走了。我只能告诉小王,“对不起,我还没有学生证,请到学生证等候区后面和排队!”。小王只好乖乖排队等候。这时,操作台上再次显示出3号,正是刚刚操作到一半的小张。小张此时还在超市里,没有必要重新进入,于是小张赶紧跑到操作台继续刚才的操作,拿了一个苹果,离开了超市,将学生证还了回去。这时,取证处的管理人员接过学生证,冲着后面的学生证排队区喊道:“我有学生证,排队的人快来拿!”走出队列,准备领卡,进入超市。可此时,操作台上显示的号码却是另外一个10号学号,10号学号拿着学生证进入了超市开始营业。操作到一半,控制台的时限再次上升,显示出小王的5号身份证。小王刚从等候取卡的队列中出来,终于得到允许进行下一步,于是他走到取卡处,“给我一张学生证!”由于学生证已经被10号拿走,管理人员只能说:“对不起,学生证还没有,请到后面的学生证等候区排队!”。见等了这么久,小王竟然先人一步。刚要发誓,他想起了学校的名言“这个世界是不公平的”,于是乖乖走到学生证等候区继续排队。10号做完手术,归还学生证后,小王又被收卡处管理员喊:“我有学生证,排队的,快来拿!”。小王走出了排队区,这时控制台终于显示出小王的5号。小王这次顺利领取了学生证,进入超市,坐在操作台上,登陆了自己的系统。小王想买苹果,于是点击了苹果产品按钮,但是系统显示苹果数量为0!小王现在想了想,有了下一步的打算:继续留在超市,有空就去控制台看看网上的苹果数量,直到有苹果为止。但是如果一直呆在超市里,想给超市送苹果的同学可能拿不到学生证,永远也拿不到苹果,这显然是不合适的。于是小王的另一个想法是走出超市,归还学生证,等下次有机会进入超市查看苹果数量,直到有苹果为止。这样虽然有机会拿到苹果,但是太累了。如果这段时间没人给超市送苹果,那这趟旅行其实就是浪费时间。于是小王想出了一个妙计。我可以走出超市,在一个不会收到控制台通知的地方等待。但是如果有人给超市送苹果,等候区就会发出信号,然后超市就有可能有苹果了。这时,我从等候区出来,伺机拨通号码。虽然苹果可能会被其他吃苹果的同学抢走,但至少不会浪费太多时间。就在超市旁边,有很多各种水果的等候区。一共有六个,分别是:苹果没有等候区,西瓜没有等候区,橘子没有等候区。苹果满是等候区,西瓜满是等候区,橘子满是等候区。小王很聪明,走到苹果不见了的等候区,等着有人送苹果的信号。这时,小孙走进了超市,给超市买了5个苹果,换了零花钱。随后,他第一时间通知苹果没有等候区,并示意“超市里有苹果!”,但小孙还没有走出超市。小王在等候区收到信号,立即走出等候区,等待被叫号码完成吃苹果的任务。可惜,小王还没来得及拨通电话,苹果就被其他几个同学抢走了,轮到小王了。小王也很聪明。考虑到这种情况,他没有直接拿苹果,而是再次查看了苹果的数量,发现苹果的数量为0,于是重复了前面的步骤,小王又回到了苹果放置的等候区。走了。接下来的时间里,小王一直在苹果没了的等候区和学生证等候区之间穿梭。小王发现吃一个苹果太难了。”信号,此时取卡区有一张学生证,控制台查询到的苹果数量不为0。终于有一次,小王成功满足了这三个条件,看到控制台上的苹果是1!小王正兴冲冲的准备按购买键,但是这时候控制台一闪,突然出现了别人的苹果编号。这个人是超市管理员,他成功的进入了超市,有一个特殊的超市管理员卡拿走了苹果,这时候苹果的数量又变成了0,然后轮到小王操作了,但是小王并不知道之前发生了什么,他清楚的看到苹果的数量是1、为了保险起见,小王查看了几次苹果的数量,发现还是1个,于是兴奋的按下了购买键!没有苹果湖。会发生这种事,于是机器爆炸,整个学校被夷为平地。几年后,学校慢慢重建。做控制台的人被枪毙了,高薪请了专家来打造,解决了之前的问题。超市又恢复正常运转了。有时超市里只有一个人,有时超市里有三个人,分别是学生、老师和宿舍阿姨。三人互不影响,相安无事。学校生活再次丰富起来。----------------------华丽的分割线------------------------这个故事包含了Java多线程的大部分核心问题,所以下面我就来复述一下这个故事。有这样一个学校(Java虚拟机),里面有很多人(线程)。我们简单的分为学生、老师、宿舍阿姨。学校中间还有一个非常棒的水果超市(临界区)。里面有一个仓库,里面存放着苹果、西瓜、橘子(临界区保护资源)。来这家超市的人,一方面可以把水果带走吃掉,另一方面可以送水果还钱。不过,超市里还有一个很奇怪的规定,就是学生只能吃或者送苹果,老师只能吃或者送西瓜,宿舍阿姨只能吃或者送橘子。这家超市的进出也很有规律。来这家超市的人必须持有相应的证件(锁物),学生需要持有学生证,老师需要持有教师证,宿舍阿姨需要持有阿姨证(锁物不同)。这三张证件每张都只有一张,存放在超市门口的证件领取处(取锁的地方,可以说是堆放的)。锁)进入。如果证书临时被别人拿走,无法获取(获取锁失败),需要在后方等待区(SychronizedQueue)排队等候证书。然后还有三个等待区,分别是学生证等待区,教师证等待区,阿姨证等待区(每个锁对象对应一个同步队列)。进入超市,更是陌生。无论是从超市取水果还是送水果,都需要一个操作台(单核CPU)来控制,而这个操作台一次只能由一个人操作。操作。为了防止有人占用控制台时间过长,这个控制台只允许一个人继续操作10s(CPU时间片),10s后屏幕上会显示一个ID,只有这个ID的人可以操作(线程切换)。至于选择哪个号码,无论是老师、学生还是宿舍阿姨都无法决定和干预,只能让控制台来决定(操作系统决定线程切换和时间分配)。不过好在每个人在控制台都有自己的账号(线程的工作内存),运行到一半就中断的数据不会丢失。这个故事的背景讲完了,下面这所学校里发生了各种各样的事情。首先我们假设进入这个学校的人都是去超市办事的。首先,人出现在学校外面(线程状态NEW),然后人进入学校(线程状态RUNNABLE)。某一刻,控制台上出现了一个数字2,这个数字通过各个学校的大屏幕通知给了所有人。于是,身份证号为2的同学小明看到了自己的号码,得知自己已经获得了进入超市操作台的权限(获得了CPU执行权),于是就出发前往超市。小明先走到超市门口,对领卡处的管理人员说:“学生卡给我!”(拿到锁)。管理人员找了找,找到一张学生证,就给了小明。小明拿到学生证,成功进入超市(成功拿到锁,进入关键区域),坐在控制台前,登录自己的账号系统(准备好工作内存,开始执行关键)区号)。小明此行的目的是带走一个苹果,于是点击苹果产品图标,系统显示还有4个苹果。于是小明成功拿走了苹果,系统记录了苹果个数-1,在系统总库(代码)中记录了新的苹果个数3。然后小明走出超市(代码执行后退出临界区),将学生证退回收卡处(释放锁),走出校园(线程状态TERMINAL),消失走进外面的人群。随后控制台上显示出3号,也通过学校的大屏幕通知了大家。3号学生小张看到自己的号码,得知自己获得了进入超市操作台的权限,便出发前往超市。小张和小明在做着一模一样的操作,只是小张的操作太慢了。刚点了一个苹果产品的图标,系统就显示下一个人的数字5。这时,小张只能被迫终止自己的操作,放弃控制台操作权(线程切换)。5号学生小王接到通知,兴冲冲地来到超市,向领卡处的管理人员询问:“给我一张学生证!”。管理人员四处寻找,发现学生证被小明拿走了,只能告诉小王,“对不起,学生证暂时无法使用,请到学生证等候区(同步队列WaitQueue)要排队!”(获取锁失败)。小王只好乖乖排队(线程状态BLOCKING)。这时,操作台上再次显示出3号,正是刚刚操作到一半的小张。小张此时还在超市里(没有释放锁),不需要重新进入,赶紧跑到控制台继续刚才的操作(线程切换,继续执行中断的代码),拿了一个苹果,离开去了超市,还了学生证(解锁)。这时,领证处的管理人员接过学生证,朝后面的学生证排队区喊道:“有学生证,排队的人快来拿!”(通知同步队列离队)。正在排队等候通行证的5号小王闻言从队列中出来,正要办通行证进入超市。可此时,操作台上显示的号码却是另外一个10号学号,10号学号拿着学生证进入了超市开始营业。操作到一半,控制台的时限再次上升,显示出小王的5号身份证。小王刚从等证的队列中出来,终于得到了下一步的许可,于是他走到取证处,“给我一张学生证!”。由于学生证已经被10号拿走,管理人员只能说:“抱歉,学生证暂时无法使用,请到后面的学生证等候区排队!”小王看到自己等了这么久被别人抢先了一步,刚想骂一句,想到学校的名言“这个世界是不公平的”,就乖乖走到学生证等候区继续排队。(不公平的锁,不是谁等的时间长谁就锁)10号操作后,学生证被退回,小王又被办证处管理员喊,“我有学生证,排队的人快来拿!”。小王走出了排队区,这时控制台终于显示出小王的5号。小王这次顺利领取了学生证,进入超市,坐在操作台上,登陆了自己的系统。小王想买苹果,于是点击了苹果产品按钮,但是系统显示苹果数量为0!小王现在想了想,有了下一步的打算:继续留在超市,有空就去控制台看看网上的苹果数量,直到有苹果为止。但是如果一直呆在超市里,想给超市送苹果的同学可能拿不到学生证,永远也拿不到苹果,这显然是不合适的。(在Sychronized代码块中循环等待)于是小王的另一个想法就是走出超市,归还学生证,等待下一次进入超市查看苹果数量的机会,直到有苹果。这样虽然有机会拿到苹果,但是太累了。如果这段时间没人给超市送苹果,那这趟旅行其实就是浪费时间。(同步代码块外循环等待)于是小王想出了一个巧妙的办法,我可以走出超市,在一个地方等待(Wait),那里不会收到控制台的通知。如果有人送苹果到超市,等候区会发出一个信号(Notify),然后超市就有可能有苹果了。这时,我从等候区出来,伺机拨通号码。虽然苹果可能会被其他吃苹果的同学抢走,但至少不会浪费太多时间。(WaitingNotificationMechanism)就在超市旁边,有很多等待区(waitingqueueWaitQueue)每种水果。一共有六个,苹果满等候区,西瓜满等候区,橘子满等候区(条件变量Condition)。小王很聪明。他走出超市,交回学生证(Wait会解锁),走到苹果消失的等候区(Wait),等待有人送苹果的信号(同步信号-wake).这时,小孙走进了超市,给超市买了5个苹果,换了零花钱。之后他马上通知Apple没有等候区,并发出信号“超市里有苹果!(AppleNotEmpty.notifyAll)”,但此时小孙还没有走出超市(Notify没有释放锁)。小王在等候区收到信号,立即走出等候区,等待被叫号码完成吃苹果的任务。可惜,小王还没来得及拨通电话,苹果就被其他几个同学抢走了,轮到小王了。小王也很聪明。他考虑到这种情况,没有直接拿走苹果。而是重新查询了苹果的个数(Wait一般匹配While条件),发现苹果的个数为0,于是又重复了前面的步骤,小王又回到了苹果。没有等候区。接下来的时间里,小王一直在苹果没了的等候区和学生证等候区之间穿梭。小王发现吃一个苹果太难了,同时还要满足。”信号,此时取卡区有一张学生证,控制台查询到的苹果数量不为0。终于有一次。小王成功满足了这三个条件,看到苹果数量控制台上是1!小王正兴冲冲的准备按下购买键,可这时控制台一闪,突然出现了别人的号码,此人是超市管理员,他凭着超市管理员专用卡成功进入了超市并拿走了苹果,这时候苹果的数量又变成了0,然后就轮到小王操作了,但是小王不知道之前发生了什么,他清楚的看到苹果的数量是1。为了保险起见,小王又查了几次苹果的个数,发现还是1个(非Volatile修饰的变量不保证线程间的可见性),于是兴冲冲的按下了购买按钮!于是,console根本没有苹果。存储区发出取苹果的指令。系统没想到会发生这样的事情,于是机器炸了,小王牺牲了(抛出运行时异常,线程释放锁终止)。几年后,原来做手术台的人被枪杀了,学校高薪聘请了专家来搭建,解决了之前的问题(Volatile)。超市又恢复正常运转了。有时超市只有一个人(进入同一个锁对象临界区的不同线程会互斥,只能有一个线程进入),有时超市会出现三个人(不同线程进入锁对象的临界区)锁对象不是互斥的)。是学生,是老师,还有宿舍阿姨。三人互不影响,相安无事。学校生活再次丰富起来。故事讲完了,虽然不能把并发编程的所有内容都讲清楚,也不能把细节处处讲得恰到好处,但却是一个很有趣的思考过程。希望大家积极讨论故事中的错误和不完善之处,一起把故事讲得更好。以下是故事内容及其含义的概要: