面向对象设计与构造第二单元博客作业
第五次作业总结
作业需求
模拟一个多线程实时电梯系统,每个楼座有对应的一台电梯,可以在楼座内 1-10层之间运行且乘客的请求均为同楼座。
代码架构
各个类的含义如下:
|- Main:主类
|- Producer:读入线程
|- Request:读入线程与调度器交互的共享对象
|- Schedule:调度器线程
|- Queue:电梯内外的队列。作为电梯外的队列,是调度器与电梯交互的共享对象。
|- Elevator:电梯线程
|- SecureTimableOutput:线程安全输出
同步块的设置和锁的选择
Request
和Queue
作为共享对象,二者提供的都是用synchronized
上锁的方法,避免同时读写带来的问题。
而当Request
或Queue
为空时,调度器和电梯分别使用wait()释放锁防止轮询;不为空时,会被notify()唤醒。
此次作业对同步代码块还不太理解,发现问题就盲目上锁,有的地方锁的范围过大,增大了阻塞。
调度器设计
调度器线程通过共享对象Request
和读入线程交互,通过共享对象Queue
和电梯线程交互。本次作业每一楼座有且仅有一个电梯,每个电梯对应一个Queue
,调度器的作用仅仅是将请求分配至其所在楼座的电梯。
UML 图与类结构
参照往年作业的迭代,设置了两级托盘,中间的调度器既是一级托盘的消费者,也是二级托盘的生产者;电梯内诸如最大载客量、运行速度,开关门时间等都是电梯的属性,方便后续定制电梯。
电梯内外的请求均使用Queue类,尽管电梯内的Queue不是共享对象。为了使电梯选择乘客更加快捷,Queue内有三个容器,一个是ArrayList,方便查找是否存在;另外两个分别是以开始楼层和到达楼层为key的HashMap,方便上下电梯。就是请求的增删过于笨拙,即使对于电梯外的队列不维护以到达楼层为key的HashMap,电梯内的队列不维护以开始楼层为key的HashMap,每次增删也至少要对两个容器操作,不过电梯内的相关操作得到了简化。
第六次作业总结
作业需求
模拟一个多线程实时电梯系统,初始每个楼座有对应的一台电梯,可以在楼座内 1-10层之间运行;新增楼座与楼座之间移动的环形电梯,可以在五个楼座之间运行;可动态增加横向、纵向电梯;乘客的请求或为同楼座,或为同楼层。
代码架构
各个类的含义如下:
|- Main:主类
|- Producer:读入线程
|- Requestlist:读入线程与调度器交互的共享对象
|- Schedule:调度器线程
|- Elevator:电梯接口
|- BuildingQueue:纵向电梯内外的队列
|- BuildingElevator:纵向电梯线程
|- BuildingQueue:横向电梯内外的队列
|- BuildingElevator:横向电梯线程
|- SecureTimableOutput:线程安全输出
同步块的设置和锁的选择
与第五次作业同步块的设置和锁的选择基本相同,将同步代码块内的不必要内容移出,最大可能地减小阻塞。
调度器设计
调度器线程通过共享对象Request
和读入线程交互,通过共享对象Queue
和电梯线程交互。本次作业每一楼座或楼层的电梯共用一个Queue,调度器负责将请求送至对应的Queue。
UML 图与类结构
考虑到纵向电梯与横向电梯的高度相似性本想将二者统一,后来考虑到环形与线性调度策略的差异,一味追求一致性反而
会增加代码的复杂度,将二者分成两个类反而简洁美观。
针对一个等待队列对应多部电梯的情况,为了避免自由竞争时多部电梯一起移动但只有一个电梯能接到人,我采用的策略是主请求分配,沿途请求自由竞争。(因为请求的随机性,电梯空转并不会带来太大的影响,写完这个优化被自己蠢到)
第七次作业总结
作业需求
模拟一个多线程实时电梯系统。横向电梯在不同楼座有开关门限制;可动态增加横向、纵向电梯,且对纵向电梯的运行速度、可容纳人数,横向电梯的运行速度、可容纳人数、可开关门信息进行定制;乘客的请求限制仅楼座和楼层不会同时相同,新增换乘需求。
代码架构
各个类的含义如下:
|- Main:主类
|- Producer:读入线程
|- MyRequest:为了实现换乘而拆解的请求
|- Requestlist:读入线程与调度器交互的共享对象
|- Schedule:调度器线程
|- ElevatorList:电梯容器类
|- Elevator:电梯接口
|- Queue:电梯外的队列
|- BuildingElevator:纵向电梯线程
|- BuildingElevator:横向电梯线程
|- SecureTimableOutput:线程安全输出
同步块的设置和锁的选择
为了换乘的实现,将电梯容器单独作为一个类,作为MyRequest和Schedule的共享对象,内部的方法也要上锁
调度器设计
纵向电梯和上次保持一致,每一楼座共用一个Queue,调度器负责将请求送至对应的Queue。对于横向电梯,考虑到同楼层不是所有电梯都能完成请求,每个横向电梯都拥有一个单独的Queue,调度器直接将请求分配至可达的电梯中,使得上次作业的电梯类无需改变。
UML 图与类结构
由于增加换乘会使得电梯的逻辑更加复杂,加之之前的策略在随机数据下并没有什么优势(电梯内有请求时,选择最远的请求作为主请求;电梯外的请求被一部电梯作为主请求后,将其从电梯外的请求中删去使得其他电梯无法访问),此次作业重写了电梯策略。电梯内外选择主请求时均默认为容器内的第一个请求。
因为电梯逻辑的简化,此次作业也没有再采用之前增删都要三次的Queue类,而是只用ArrayList来存。
换乘操作则是在请求输入时就根据起始位置和电梯队列进行拆分,每次乘客出电梯如果没有到达最终目的地该请求就会被重新加入等待队列进行二次或三次调度。
UML协作图
BUG分析
第五次作业
-
主请求未进入电梯而电梯已经满员导致的bug。确定主请求且主请求未进入电梯时,我的逻辑是电梯始终向主请求出发楼层移动,途中捎带同方向请求。当主请求还没进入电梯电梯就满员后,我的电梯由于接不到主请求,会在主请求的出发楼层反复离开和返回而死循环。
修复:当电梯满员后就将主请求更换为电梯内的请求
-
输出线程不安全导致的bug。
修复:增加了线程安全的输出类
第六次作业
本次作业无bug
第七次作业
-
调度请求时没有考虑电梯可达性导致的bug。请求输入时按照电梯可达性进行拆分,但调度时没有判断可达性,可能会加入同楼层不可达的电梯内导致死循环。
修复:调度时增加可达性的判断。
Hack策略
考虑到本单元随机测试效果不佳,三次作业的测试数据主要都是自己手动构造,集中在少量的楼层和楼座,有较强的针对性。
心得体会
本单元的成绩不太理想,没有充足的时间对自己的代码进行测试。除了输出线程不安全的bug,其余的bug都与多线程无关,一次是边界情况考虑不全面,一次就单纯是二次复工时的遗漏。
本单元最大的收获自然是如何使用多线程,如何实现线程安全,考虑到自己没有写出线程不安全的代码,这点倒是学有所获。