Java多线程之熟睡的理发师问题
理发师问题也是经典问题之一,最经典的三个线程同步互斥问题是:生产消费者问题、哲学家就餐问题、读者写者问题。这三个问题的解决方案有很多,不同的方法也有不同的含义和原理,需要好好理解。各个方法中,信号量是通用的方法,理发师问题也可以用信号量来解决。
操作系统原理之进程和线程管理
Java的多线程和并发知识纲要
Java多线程之生产者和消费者模式
Java多线程之哲学家就餐问题
Java多线程之读者写者问题
Java多线程之吸烟者问题
问题描述
某理发店里只有一名理发师,一张理发椅,若干张顾客等待椅。理发师每天除了理发就是睡觉。理发店的生意很好,需要理发的人很多。
如果一名需要理发的顾客,进店发现等待椅坐满了,他就走了,决定稍后再来。
如果发现等待椅子上还有空位,他就走进店里坐下来等待,不过只会等待一会(每位顾客能等待的时间不一样),如果超过时间就放弃等待,离店并稍后再来。
坐在等待椅上的顾客会不断注意理发师的状态,如果他不在理发而在睡觉中(哪怕刚睡着),就会尝试去叫醒理发师。
叫醒理发师的顾客可以理发,但是需要抢占理发椅,只有抢占到理发椅的顾客才能离开等待椅,并坐到理发椅上由理发师理发。
理发师理完发后,顾客起身离开理发椅,并离开理发店,对理发师的手艺很满意决定不久后再来。
理发师一理完发就会睡觉,由于理发师只能由等待的顾客叫醒,所以如果理发店如果没有顾客的话理发师将一直睡着。
理发师想睡觉只能睡在理发椅上,所以只有当理发师醒了,顾客才能去抢坐理发椅。
问题分析
理发师问题看上去比较复杂,不过仔细分析,化繁就简还是能解决的。
假设顾客一直想理发,哪怕是刚刚理完发离店后不久还会再来。
顾客的状态:门外状态、等待状态、理发状态。
理发师的状态:睡觉状态、理发状态。
椅子(等待椅和理发椅)的状态:有人、没人。
状态变化分析:
1、在等待状态的顾客会一直尝试唤醒处在睡觉状态的理发师。
2、在门外状态的顾客会每隔一段时间尝试进店,成为等待状态。
3、在等待状态的顾客可能会等待一段时间后离开,变成门外状态。
具体做法分析:
1、顾客尝试离开或坐入等待椅:
等待椅的数量是一个临界资源chair,只能同时由一名顾客去操作。
2、顾客进门尝试坐在等待椅上:
需要一个进入店门的信号量door,尝试进门的顾客先去door排队(P),获得door的顾客才能进店。
进入店后查看等待椅的状态,如果椅子满了,就转身离开,并归还door信号量(V)。
如果椅子没坐满,那么将等待临界资源chair,修改chair-1。修改好chair后才会归还door信号量(V)。
这样的操作确保进店(拿到door)的顾客一次只有一个,且要么能坐到等待椅上,要么不能。
3、等待中的顾客唤醒理发师:
坐在等待椅上的顾客只做一件事,如果理发师在睡觉就唤醒它。
4、顾客抢占理发椅:
成功叫醒理发师后,所有的等待中的顾客都会尝试去占有理发椅,但是只有一个顾客能成功占有理发椅。其余顾客将继续保持等待状态。
理发椅是临界资源,在理发师清醒状态下,只能由一名顾客占有。
占有理发椅的顾客将进入理发状态,理发完成之后顾客离开理发椅。
我的解决
各个对象的状态其实是为了方便理解,在实际代码中并不会使用到。
理发师:
1 | package com.chain.test.day07; |
顾客:
1 | package com.chain.test.day07; |
椅子(等待椅):
1 | package com.chain.test.day07; |
理发椅:
1 | package com.chain.test.day07; |
主测试方法:
1 | package com.chain.test.day07; |
程序持续运行较长时间后的某一个片段截图(忙碌的理发师,只能小憩一会):
程序正常退出时的运行结果:
每个顾客理发的次数也是近似相等的。
原先的代码会导致运行一段时间后出现一个顾客都无法得到理发师理发机会的情况,排查了好久才发现原来的代码会出现一个极端情况,即顾客坐上理发椅后刚换醒理发师,理发师接着自己又睡过去了。从打印输出的结果也可以看出,在某一个时刻,顾客刚刚sit到work-chair,而后紧接着理发师sleep了,再之后就出现所有的顾客无法理发的情况。现在已经解决了这个问题,并重新更换了截图和相关代码。
总结思考
将复杂问题分解,分解的过程中自己拿起笔,用文字和图片将抽象的问题形象化,具体化,清晰化。
多线程问题的锻炼也能提高自己在编码时思维的严谨,简单的步骤也需要打磨很久。
在多线程和并发下,各种意想不到的情况都会发生,需要平时的经验积累和深入思考。