OO-第二单元总结
第一次作业
类图
时序图
Main
RequestMonitor
RandomStrategy
NightStrategy
Elevator
设计思路
- 线程
第一次作业创建了4了线程(除去绘图线程):主线程、请求监视器线程(
RequestMonitor
)、调度器线程(Strategy
)、电梯线程(Elevator
)。RequestMonitor
和Strategy
共享RequestTable
,Strategy
和Elevator
共享ActionsQueue
。
- 请求队列
RequestTable
& 将请求根据方向、from楼层、to楼层分类。
riseRequests
和downRequests
因设置了get方法产生了线程安全问题,在之后的作业中被替换为getKeys。
&RequestTable
提供了synchronzied
修饰的addRequest
、ifHas
和getIn
方法,addRequest
会唤醒在RequestTable
对象等待的线程(调度器线程)。getIn
方法调用了HashMap
的remove
方法,所以调度器可以调用ifHas
确定是否有需要的数据,使用getIn
获取需要的数据并通过addRequest
将多余的数据放回RequestTable
,而无需考量同步问题。BUG: 在之后的多电梯作业中,由于我采用了一调度器一电梯的方式,导致某一调度器ifHas
与getIn
之间会出现所需数据被其他调度器线程抢占的可能,而调度器有时仅使用ifHas
方法,所以两者的实现不能合并,因而需要在调度器中添加同步代码块。
&RequestTable
维护了一个allOver
布尔值代表新请求的终结,调度器通过调用noOne
和isAllOver
方法来确认是否停止或等待。
ElevatorAction
、ElevatorStatus
& 电梯运行状态、电梯运行动作的枚举
- 动作队列
ActionsQueue
& 主要维护两个FIFO队列,分别代表“已确认执行的电梯动作”、“动作所对应的请求”方法
addAction
通过synchronzied
修饰requests
,addPerson
通过synchronized
修饰persons
,调度器通过这两个方法向动作队列中添加数据,并唤醒在ActionsQueue
等待的线程(电梯线程)。方法getNextAction
与getNextPerson
与之对应,来使得电梯线程获取下一个动作。
& 维护一个allOver布尔值来代表新动作的终结。
&RandomStrategy
每次向动作队列中添加动作时,需要唤醒电梯线程并等待电梯线程执行完所有动作。NightStrategy
只在最后一次添加动作时唤醒电梯线程并终结。
RandomStrategy
调度设计
通过
makeNextAction
确定主请求,在完成主请求的路程中每一层调用方法needOpen
确认是否需要捎带或出电梯。主请求确认的逻辑:若电梯靠近低楼层,且低楼层存在请求,则以低楼层的某一请求为主请求,否则反之。
- bug修复
& 因为
RequestTable
的getIn
机制,某次使用该方法的时候忘记将多余的请求放回,导致请求丢失的情况。
第三次作页
类图
时序图
Main
StrategyController
RandomStrategy
Elevator
更改
RandomStrategyController
&
RandomStrategyController
共享RequestTable
, 当有add请求的时候被唤醒,通过ElevatorFactory
和不同的内部RandomStrategy
类来创建调度器和电梯线程。
RandomStrategy
& 这次作业将调度器类修改为抽象类,其中抽象方法
init
用来初始化调度器的类型、最大载荷量,floors
方法用来判断一个楼层是否是可达的,canOther
方法用来判断一类请求是否可由更快的电梯单独完成,transferStation
用来获取一个“不能由更快的电梯单独完成的请求”的“比不换乘更节省时间的换乘站”。具体实现在Controller类内部。
- 多电梯设计方式
& 由Controller创建不同类型的调度器+电梯线程,实现不同的换乘逻辑,所有的调度器共享一个
RequestTable
对象,各线程通过canOther
区分一个请求是否需要自己来完成,通过transferStation
获取一个请求的换乘方案。
& 对于被一个电梯“看中”而未进入电梯的请求,将其存入
RequestCache
对象。
& 对于一个需要换乘的请求,通过HashMap将第二段请求保留,并在第一段请求完成时放入RequestTable
- 可拓展性
& 通过工厂模式创建不同的电梯线程,将调度器类的关键逻辑(换乘逻辑,请求划分逻辑)放入抽象方法中,仅在有具体调度需求的时候实现,并通过内部类的模式,可以在不同的实现中互相调用不同调度器的方法实现,因而具有较好的拓展性。对于调度逻辑,仅需修改
makeNextAction
、needPeopleIn
等少量关键方法即可。
- bug修复
& 第三次作业遇到了一部电梯无限下行的bug,通过预先写好的assert很容易发现了bug所在!
Hack方式
& 采用纯python脚本评测的方式,实现自动编译、打包、随机数据生成、数据合法性判断、jar包运行、正确性/运行时间检测、错误定位。
& 测试强度依赖于生成数据的逻辑,随机性比较强。
心得体会
由于本单元涉及到多线程编程,所以在第一次作业实现过程中遇到了各种各样的bug,后来重新巩固了涉及到的诸多概念,逐渐熟悉了多线程编程模式和调试方法,在后两次作业中基本上没有遇到线程上的bug。由于第一次作业中便考虑到了层次化设计,所以在之后的作业中修改起来比较容易。