OO总结之多线程电梯

OO总结之多线程电梯


一. 设计策略

二. 扩展性分析

三. 结构分析

四. Bug分析

五. 总结


一. 设计策略

1.1 第一次作业设计策略

第一次作业的目标是设计一套单部电梯调度系统,并要求可捎带,性能与ALS相近。我的设计方案是主体分为四个部分:主类MainClass调度器类Manager,电梯类Elevator等待队列PrioQueue

主类:主要负责对象的生成以及线程启动,值得一提的是,我的设计中输入请求的解析,即输入线程是在主类进行的,因为主类在生成对象,启动线程之后就没有需要做的工作了,然而为了遵循主类最先开始,最后结束的原则,同时为了设计的方便,就让主类一直解析输入并传给调度器,直到接收到终止命令,随后向电梯发送终止命令,关闭输入,终止程序。

调度器:非线程类,主要负责请求的入队,以及电梯的运行调度。该类接收从主类传入的请求,将其加入等待队列的同时依照情况决定是否启动闲置的电梯(在此次作业中就是直接启动),这也是为何不直接将请求传入等待队列。调度器在接收到终止命令后,会告知电梯将其终止信号设置为有效,等待电梯自行寻找合适时机终止线程。调度器同时负责告知电梯下一步的运行方向,在此次作业设计时,本方法被设计为静态方法,电梯调用时传入当前楼层即可。

电梯类: 线程类,主要负责从等待队列中收集请求,并根据调度类的指示方向运行,同时扫描途径楼层决定是否进行上下电梯操作。我所设计的电梯类维护一个内部请求队列,当电梯从等待队列中扫描到当前层请求之后,执行开门操作,然后接受等待队列传入的请求组,调用analyse方法逐个解析请求,按照楼层索引加入内部队列,内部队列用HashMap实现,键值为目标楼层。当电梯从内部队列扫描到当层请求之后,之行开门操作(若既有人上,也有人下,开闭门请求合并),调用leave方法逐个解析请求,输出内容,请求完成。每当电梯扫描完当前楼层的等待队列与内部队列之后(无论是否开门),会向调度器询问下一步运行方向direction,然后仅根据这个值确定下一步运行方向,电梯无需关心自身如何调度自身,一切听从调度方法即可。

等待队列:非线程类, 主要负责维护等待的请求,队列使用HashMap实现,键值是请求起始楼层。等待队列向询问的对象返回当前所询问楼层是否有请求做出回应,并且可以使用getRequest方法返回一个集合了本层全部请求的ArrayList。在新请求入队时,等待队列类会解析请求,并根据请求的起始楼层加入队列的相应键值对。

调度策略及实现细节: 本次电梯采用了LOOK算法,当电梯询问时,若运行方向上还有等待请求或者内部请求,则运行方向不变,若否,则电梯改变运行方向。这种调度策略性能相对稳定,并且即使不检测约束方法,由于请求自身的合理性,所以也不会产生异常结果。若请求队列和内部队列均为空,则设置direction为0,即电梯暂停。同时在方向改变时,电梯类会调用notifyAll方法唤醒等待的运行线程,这样可以唤醒停留的电梯。当输入停止时,调度类向电梯发送中指命令,电梯将内部terminate值设为true,当电梯停止时,会检测terminate是否为真,若真则终止线程。

1.2 第二次作业设计策略


第二次作业从单部电梯变为多部电梯,并且增加了电梯容量限制。这次的设计与上次相比,没有增加新的类,只是修改了相应细节。修改如下:

主类: 增加了电梯数量的解析以及创建电梯组。

调度器类:调度器维护电梯组,每个单独电梯的调度策略和上次相同,但是当电梯容量满后,不再响应等待队列的请求,而是一心一意服务内部队列。当新请求传入时,调度器会查看是否有电梯停止,若有停止,则会唤醒停止电梯,但是电梯总体调度是竞争式的,就是那个被唤醒的电梯可能会白忙一场

电梯类:增加了容量限制,并且在申请等待队列时会传入剩余空间数。

等待队列:在向询问者提供请求组时会依据传入的剩余空间决定返回适量的请求。

总体细节: 本次调度策略和上次相同,架构上也没有太多变化,只是适配了新的需求。

1.3 第三次作业设计策略


本次作业为每一个电梯进行了个性化定制,有限制楼层,并且可以随时添加。本次作业的设计增加了两个类,一个是工厂类ElevatorFactory,另一个是智能请求SmartRequest

主类:增加了电梯类请求的解析。

智能请求:这个类由官方的PersonRequest变换而来,调用方法和PersonRequest类完全相同,所以其他的调用方法完全不需要更改。本次作业中所有的调度请求都是智能请求。智能请求是一种保存了调度策略的请求,在电梯解析智能请求时,会将目标楼层变换为换乘策略的中转站(若不需要换乘,则直接是目标楼层),在电梯将请求执行完成时,会调用智能请求的isArrive方法检测当前请求是否到达最终请求,若否,则调用update方法更新智能请求,智能请求可以变成另一个请求,重新加入队列。

调度器类:根据电梯类型不同,本次作业请求分为三个队列维护,每部电梯调度策略和第一次相同,并且根据不同类型电梯响应不同队列的原则,总体策略和第二次作业方法相同。同时本次作业方向指示方法需传入电梯自身,然后调度器负责解析这个电梯的类型然后做出相应的调度。

电梯类:增加了定制化参数,并且在执行离开电梯操作时会对离队请求检测是否完成,若否,则请求更新,重新入队。

工厂类:封装了电梯的工厂方法,根据传入的电梯请求返回相应的电梯。封装了请求的变换方法,将传入的ElevatorRequest变为SmartRequest

等待队列:根据三个电梯类型维护三个队列,方法和第二次相同。

总体细节: 本次作业的换乘策略我在讨论区一种简单的以正确性为优先的换乘策略中做了完整的描述,电梯调度策略与前两次相同,由于引入了智能请求,所以队列和电梯完全不需要关心执行的是什么,完全当做前两次的普通请求即可。

可扩展性分析

第三次作业我完成了一个相对成熟并且多功能的设计,其中调度策略和性能的扩展性我在上文所说的讨论区文章中已有所论述。从架构上,采用了工厂方法,使电梯的数量不存在上限,并且由于调度器是针对每一个电梯做独立指示,且没有耦合,因为调度器也不需要考虑当前请求如何实现,所以电梯在内部请求执行完毕之后可以安全提前退出,所以可以减少电梯数量。本次我的设计采用了性能平衡式设计,即采用性能稳定的LOOK算法和单向优先少换乘的策略,避免了极端情况下电梯调度过于复杂导致总体性能剧烈下降(比如带着一电梯人去接一个人),这一点从性能得分上也可以看出(基本可以达到99或98,表示性能可接受)。

结构分析

类图如下所示:
OO总结之多线程电梯_第1张图片

本次作业中的线程类只有主类和电梯类,所以即使在调度器,电梯类和等待队列中有循环引用,也没有出现任何线程安全问题。

类分析:

方法分析:

OO总结之多线程电梯_第2张图片
OO总结之多线程电梯_第3张图片

数据使用DesigniteJava工具生成。

Bug分析

本次作业的迭代是比较成功的,并未出现大面积重构现象,故未产生重大程序错误。并且由于尽可能少使用线程,以及同步块控制得当,并没有造成线程安全问题。
第五次作业由于在电梯充重启时,由于采取的启动方法过于草率(即设置方向为1),导致电梯上到了不存在的楼层,被互测发现。(这个问题为什么强测没测出来呢?)
第六次作业并没有发现bug
第七作业由于换乘请求涉及到先出电梯后进入,会导致有一个时刻所有电梯里没有人,这时如果输入终止,电梯就会误以为所有请求完成,导致电梯提前退出,导致剩余请求无法完成。解决这个bug的方法是为调度器设置终止暂存技术,即输入终止后,调度器不会立刻向电梯发出停止指令,而是要检测当前的所有电梯是否停止并且等待队列为空,只有满足上述条件才会设置终止。每一次电梯停止之后,都会向调度器发送一个消息,调度器会检测是否达到终止条件,并决定是否向电梯发出终止指令。

总结

本次电梯作业的迭代设计是比较成功的,而且在设计期间充分注意了类的解耦,极大地简化了电梯的功能,使其在后序作业中无需调整功能。且所有的调度策略和换乘策略全部隐藏封装,让调度和电梯以及队列类都无需关心请求完成的方式。本次作业的线程安全问题也得到了很好的预防,在测试中并未出现,并且在本地编程时也未造成过大困扰,给我最大的启示就是:线程安全不是靠容器,而是要靠设计,如果能在设计层面考虑好可能出现的风险,就可以防止线程问题的产生。本次作业的迭代成功也在作业消耗时间上体现出来了,本次作业除第一次外,其余的基本都可以在一天之内完成,并且弱测提交均不超过两次。

希望下一系列作业时也可以这样冷静分析,顺利完成。

你可能感兴趣的:(OO总结之多线程电梯)