OO第二单元总结
目前OO第二单元电梯作业的训练已经全部结束了,第二单元的主题是JAVA多线程,通过电梯这个我们日常生活中广泛使用的例子来训练多线程编程以及线程安全的相关问题。总的来说,完成第二单元的题目比第一单元稍微困难一点,不是在于算法等的设计,而是在于如何发现bug及解决bug。下面对我的三次作业进行逐一分析。
第一次作业
本次作业需要完成的是一部可稍带电梯,从顶层设计上,需要用两个线程:输入线程和电梯线程,以及一个调度器维护请求队列。
UML图:
- 总共分为5个类
- Main: 主类
- Request: 乘客请求类,用于管理请求。完成作业后反思发现这个类其实并不需要
- InputHandler: 输入处理类,将输入请求放入请求队列
- Scheduler: 调度器,维护请求队列及向电梯提供请求
- Elevator: 电梯类,执行请求
复杂度:
代码行数:
这次作业总的来说复杂度其实并不高,但是我在类及方法设计上还是存在一些问题,Elevator类中的两个函数本应由Scheduler维护,即两个检查捎带函数。例外Reuqest类完全是一个多余的类,开始设计这个类的初衷是记录每条请求到达的时间,但后来发现其实并不需要记录,扫描请求队列时,不满足时间要求的请求自然不会出现在队列中,本次作业中遇到的一个难点就是对notify以及wait的使用,当时写的时候一个多小时各种改怎么都不对,查找了很多资料才解决这个问题。其次还有一个线程如何结束的问题也困扰了好久。
本次电梯是一部可稍带电梯,我设计的捎带策略也比较简单,每到一个楼层就扫描请求队列检查有没有满足捎带条件的请求,例外也不会返回去接最近楼层的请求。还有一个捎带策略就是,电梯到某一层如果人全部下光了,会从请求队列中随机选择一个当前楼层为起始楼层的请求作为主请求,这种方法可能造成在某些情况下性能很低。
bug分析
本次作业在公测和互测中都没有被找出bug,还是比较不错的,但是可能还是由于上面所说的选择请求策略太过简单的问题,强测好几个点性能都是0分。在互测过程中也没有找到其他人程序的bug,可能由于本次作业调度比较简单,时间要求也比较宽,例外没有评测机手工启动线程测试效率也着实比较低。
第二次作业
本次作业是多部可稍带电梯,并且可以规定电梯数量,每个电梯有最大乘客数,在实现过程中总的架构和第一次作业提出的并没有什么太大的变化,只是增加了一个电梯工厂类启动新增电梯,以及解决了上面说到的第一次作业存在的设计问题。
UML图:
- 总共分为5个类
- Main: 主类,启动输入、电梯及调度器
- InputService: 输入线程,用于读取电梯数量和请求
- StartElevator: 该类用于启动电梯,根据读取的电梯数量启动相应的电梯
- Scheduler: 调度器,维护请求队列以及分配请求给电梯
- Elevator: 电梯类,执行乘客请求
复杂度:
代码行数:
这一次作业和第一次作业就只有两点不同,多部电梯以及由电梯最大容量的限制,总的来说第一次作业能顺利完成的话,这次作业并不难,而且时间要求更低。在这一次作业中修改了上述第一次作业存在的问题,删除的Request类以及将捎带请求的检查放入到调度器中,使代码结构更加规范。
关于这一次多部电梯的调取,我采用的调度算法非常简单,就是让每个电梯自己去抢请求,没抢到就wait,然后单部电梯就进行ALS调度。但是在强测中的所取得的性能结果却出乎我的意料,接近80%的点都达到了95以上,甚至还有3个点是满分,仅有一个点性能为0分。
bug分析
这一次作业在公测和互测中也是都没有被找出bug,可能也是互测中对请求的时间规定比较严格吧,我这种小儿科调度也还是可以达到要求。
第三次作业
这次作业相对于前两次作业来说,难度无疑是上了一个非常大的台阶。不仅电梯类型变为了3种,最坑的是停靠的楼层问题,电梯的速度在调度上对性能提出了很大的要求。在完成这次作业的过程中也是对程序结构反复更改,尤其是在最后电梯线程的结束问题上,废了很大周折。
UML图:
- 总共分为6个类
- Main: 主类,启动输入线程、初始3部电梯线程以及调度器
- InputService: 输入线程,读取乘客请求和电梯请求
- ElevatorFactory: 电梯工厂类,根据电梯请求新增相应电梯
- Scheduler: 调度器,维护请求队列和电梯队列
- SafeOutput: 输出类,保证输出安全
- Elevator: 电梯类,执行请求
复杂度:
代码行数:
在本次电梯作业的设计环节和实现环节中,我遇到了不少问题。首先一个问题是在运用观察者模式的时候,电梯作为每一个观察者,知道调度器的所有状态,调度器保存着所有电梯信息,那么调度器是否需要设计为一个线程来实时与各部电梯进行交互呢?最开始我也是这样设计实现的,在实现的过程中,有遇到各种各样的bug,比如说莫名奇妙请求没了,也没有分配到任何电梯中,可是我明明做好了同步控制的。其次在各线程运行过程中,怎么结束也成为了一个非常棘手的问题,在调度器为线程的情况了我设计了各种各种的结束方法也都无济于事,最后放弃调度器线程,采用前两次的策略,由电梯去访问调度器。在一个就是怎么结束的问题,在讨论过程中,我发现很多同学也都遇到了这个困难,只有再所有人都到达目的楼层后电梯线程才能全部结束,故需要一个变量去记录到达目的楼层的请求数量,与原来的请求数量进行比较,若相等才说明所有人已抵达目的地,电梯才能关闭。在这种情况下我又遇到了一个问题,当检测到还有乘客没有送达时,假设只有一部电梯在运行,其他电梯在wait状态,即使送达了之后,那部运行的电梯线程可以顺利结束,其他电梯仍然wait,最后想到的一个解决办法时,电梯结束时notify所有wait线程。
从可拓展性来讲,我采用的是多部电梯统一建模,电梯内有build()方法,根据电梯类型,来决定电梯之间不同的属性如可停靠楼层、载客量、速度等,所以可拓展性应该还是比较好的,如何需要新增属性,更改build()方法即可。
bug分析
本次作业我存在一个非常严重的bug导致强测时翻车,20各点全部rtle。分析代码得知bug根源,我设计的换乘楼层最低1层,最高15层。若有类似FROM-15-TO-20或FROM-1-TO-13这类只能由A电梯送达的请求,会被B类或C类电梯执行,请求无法交付和换乘,电梯无休止的运行下去,造成rtle。对于自己为什么会产生这么严重的bug,还是由于在最初的设计中调度器和电梯功能混杂造成的,在我的方案下,就不应该由调度器去主动分配请求给电梯,而应该始终保持电梯无人时去找调度器申请的原则,调度器去选择合适的请求,没有的话电梯就继续wait。
总结
在这三次作业中,不管是架构设计还是线程安全设计都是非常重要的问题,本单元主要训练的时多线程相关,线程安全问题的设计,个人认为自己在这方面还是做得比较好的,至少没有出现线程安全或死锁相关的bug。但是在实现代码时还是要多多注意细节上的问题,这次第三次作业的一个bug导致全军覆没属实可惜!