OO第二单元总结
OO第二单元总结
设计分析
在第二单元多线程电梯作业中采用了公平竞争的电梯调度策略,聪明的电梯各凭本事,谁接到人算谁的。属于改进的轮询,通过输入模式的不同、电梯种类的不同设置不同的sleep策略,以适应有限的CPU时间。
共实现了调度器Schedule
、线程安全类ElevatorInputThread
、电梯线程类Elevator
以及用于控制输出的Print
类,输入线程获得请求并解析,使调度器收到相应请求并加入到对应容器中,在电梯需要调度器内的属性时用于访问。
线程安全分析
第一次作业中由于只有一部电梯,故只在调度器中需要put请求时加锁,使存储用户请求的request容器、存储某楼层的进出人员id的inPeople、outPeople容器在被访问时是线程安全的。
第二次作业有三部电梯,且能够增加电梯,因此在调度器中响应put请求以及add电梯请求时需要加锁。此外,当电梯在某楼层停止需要询问是否进出时访问了调度器内部的容器,因此也要加锁。
第三次作业则在第二次作业的基础上整体简化了代码,以电梯在某楼层停止的逻辑实现stopEle方法,使得代码行数显著下降,同步块只需设置在stopEle方法上即可。
由于本次作业大致采用了轮询的策略,并未使用wait-notify语句。
调度器设计
由于三次作业的调度器Scheduler
实现基本逻辑类似,故只说明第三次作业的调度器设计。
设置了数组request
存储由输入线程获取的请求,以及HashMap
容器inPeople
、outPeople
存储各楼层可能需要进入或者离开电梯的用户id,value
容器用以标记以及出入过的用户,以及elevators
用以增加不同pattern
的电梯。
实现了put
以及addElevator
方法用以增加用户乘坐电梯的请求以及增加电梯的请求,这两个方法是设置了synchronized
关键字的线程安全方法。
电梯类使用了调度器存储的属性,并且在run
方法内实现了对于自身进行公平竞争的调度。所有电梯公用一个Scheduler
。电梯在运行时根据模式的不同在一层睡眠不同的时间,以应对不同情况,然后在请求非空且调度器内请求未全部完成的情况下进行轮询、等候操作。实现了look
算法,若楼层在运行方向上没有用户相关请求则直接转向,否则继续向该运行方向运动。在某个楼层调用stopEle
方法找到需要进入或者离开电梯的用户时,根据调度器内的value
、inPeople
以及outPeople
三个数据确定需要进出的用户,并根据情况具体分类为只有进、只有出、有进有出等,不会产生白白开关门的时间浪费,采用Print
类输出,目的是在debug时增加输出电梯的类型,以更清晰地判断电梯是否到达错误楼层等逻辑错误。
架构设计的可拓展性
UML类图
第一次作业
第二次作业
第三次作业
UML顺序图
三次作业的时序逻辑相同:
功能设计与性能设计
我的电梯架构设计由于采用了自由竞争的策略,可拓展性是比较好的,但是由于在第三次作业才对复用性高的代码进行了整合,导致最终的可拓展性约等于0。
在第三次整合时,使得代码简洁了许多,如果从第一次作业就及时修改复用性较高的代码,那么从两次迭代的修改部分会大大减少。
在第二次可以增加电梯数量以及第三次增加电梯种类的情况下,具体的修改在于调度器需要实现增加电梯请求,以及电梯调度部分的修改。在整合前由于电梯停靠楼层的逻辑不合理,出现了白白开关门的情况,因此关注细节部分并分类讨论,实现stopEle
方法,使得性能有所提高。同时在第三次迭代时对应不同的电梯实现了不同的arrive
方法,这种设计如果从第一次作业就开始使用将大大提高可拓展性。
关于调度算法,在自由竞争的情况下只需要改变合理的等待时间即可降低轮询带来的CPU消耗以及提高性能。
bug分析
自己程序出现的bug
弱测均未出现bug,第一次作业以及第二次作业出现的强测bug主要有两种:调度效率过低导致的超时以及轮询过于频繁导致的CPU耗尽,由于条件不允许,程序并未产生死锁等bug。在第二次作业中也被hack了3次,这两种情况的平衡是公平竞争策略的重要方向,而在第三次作业并未产生此两类bug,可以发现公平竞争策略在合理的等候以及电梯数量多时性能还是不错的。
Hack策略
由于自己采用的是自由竞争策略,在互测中不好意思刀别人(bushi)。
心得体会
在第二单元的学习中体会到了多线程的美妙,在第一次作业中曾经重构尝试了notify-all的模式,但是在性能上实在太低,也有可能是自己的look
算法过于naive。因此最终决定采用自由竞争的方法,让问题变得简单了许多。
在重构时被线程安全问题折磨了不少,同步块的错误使用、遗漏导致的线程不安全问题以及死锁问题导致bug频出,但是通过debug初步复现多线程的运行过程能够理解这些问题的产生。
三次作业迭代的过程还是比较轻松,自己在第三次作业也实现了复用度高的代码的整合以及输出类的分离,使得代码可读性还是不错的。不足之处在于调度位于电梯类内部,只是使用了调度器内的数据,在根本上并未实现调度器与被调度类的分离,在这点上的设计还有不足。