OO第二单元博客
OO第二单元博客
第一次作业
1 整体架构
本次作业的UML图如下所示:
2 需求容器设计
本次作业中,每个楼座都由一个对应的RequestTable。每个RequestTable中,按照出发楼层将需求分别存放在10个队列中,这10个队列则由一个HashMap存储,其中Key是楼层,Value是队列。
3 调度器设计
我在设计时并未实现专门的调度器,而是直接将调度策略实现在了Elevator的run()方法中。因为个人认为本次作业中调度器没有任何必要,反而使代码显得臃肿。我的调度策略与非常流行的Look策略大致相同,在强测中也取得了不错的性能表现,具体如下:
目的楼层的确定
- 若电梯内有乘客:以电梯内乘客当前方向上的最远楼层为目的楼层
- 若电梯内没有乘客:
- 若当前方向上的楼层有等待中乘客:以当前方向上最远的乘客所在楼层为目标楼层
- 若当前方向上的楼层没有等待中乘客:转向,并按照上述方式确定目标楼层
捎带
电梯前往目标楼层的过程中,对途径楼层中目标方向与电梯运行方向相同的乘客进行捎带
等待
当且仅当满足以下三个条件,电梯进入等待:
- 电梯内没有乘客
- 当前楼座RequestTable为空
- 输入未结束
4 线程交互
采用生产者-消费者模式,以Input为生产者、Elevator为消费者,二者通过共享对象RequestTable进行交互。
5 线程同步
采用了非常简单粗暴的同步方式:
- 对共享对象RequestTable的每一种方法加synchronized
- 每个方法结束时notifyAll()
6 Bug
- 强测:参与强测的版本中,由于在捎带时未考虑乘客的目标方向,导致了RTLE。
- 互测:未出现Bug,也未发现他人Bug。
第二次作业
1 整体架构
本次作业的UML图如下所示:
2 需求容器设计
本次作业中,用一个RequestTables类统一管理所有的RequestTable。每个楼座对应一个RequestTable,与上一次不同的是,每个RequestTable中包含2个HashMap,分别对应上行和下行的需求,HashMap与第一次设计相同。横向电梯的设计类似,每个楼层同样对应一个RequestTable,不同的是每个出发楼座对应一个HashMap,HashMap的Key为目的楼座。
纵向电梯RequestTable的结构为:
方向\出发楼层 | 1 | 2 | ... | 10 |
---|---|---|---|---|
上行 | ||||
下行 |
横向电梯RequestTable结构为:
出发楼座\目的楼座 | A | ... | E |
---|---|---|---|
A | |||
... | |||
E |
3 调度器设计
- 单个电梯:纵向电梯调度策略与第一次相同,横向电梯也只是在此基础上对环形的情况稍作调整,不再赘述
- 多个电梯:多个电梯之间采用自由竞争的策略,即不专门为电梯分配任务,先到先得。好处是实现简单、不易出错,坏处是可能导致电梯“白跑一趟”的情况,导致浪费。该策略在强测中性能表现较好。
4 线程交互
依然采用生产者-消费者模式,整体上与上一次相同。对于新增的多个电梯的情况,同一楼座或楼层的电梯共享同一RequestTable。
5 线程同步
与上一次相同。
6 Bug
本次作业在强测和互测中未出现Bug,也未发现他人Bug。
第三次作业
1 整体架构
本次作业的UML图如下所示:
2 需求容器设计
与第二次作业结构相同,只是将存放的对象由PersonRequest改为了自定义的乘客类Passenger。使用乘客类的目的是分解乘客的需求,从而方便电梯进行处理。分解规则如下:
- 若出发楼座与终点楼座相同,或出发楼层与目的楼层相同且存在可用(即在出发楼座和目的楼座上都可停靠)的横向电梯,则不进行分解;
- 若出发楼层有可用的横向电梯,则分解为FromFloor上的FromBuilding→ToBuilding和Tobuilding上的FromFloor→Tofloor两个需求;
- 若终点楼层有可用的横向电梯,则分解为FromBuilding上的FromFloor→ToFloor和ToFloor上的FromBuilding→ToBuilding两个需求;
- 若不满足以上条件,则先找到最近的可用横向电梯并记其楼层为TransFloor,将需求分解为以下三个子需求:FromBuilding上的FromFloor→TransFloor、TransFloor上的FromBuilding→ToBuilding以及ToBuilding上的TransFloor→ToFloor。
3 调度器设计
与第二次作业相同。
4 线程交互
与第二次作业相同。
5 线程同步
与第二次作业相同。
6 Bug
横向电梯等待条件设计不合理导致轮询,出现CTLE。具体而言,我所设置的等待条件是当层没有等待中乘客且电梯内没有乘客,那么假设某层只有一个乘客,该乘客的需求是A→B,若1号电梯可以在A、B开门,而2号电梯不可以,那么在1号处理完成该需求的过程中,2号电梯既不能处理该需求,又不能进入wait(),而是会不断地判断是否可以等待,即轮询,从而导致了CTLE。
互测中发现了一些他人的Bug,主要包括线程未正常结束而导致的RTLE、同步不当导致异常等。
UML协作图
这里给出第三次作业的UML协作图:
心得体会
本单元的作业是我第一次接触多线程编程。通过这次作业,我进一步加深了对多线程的理解,并初步掌握了多线程编程的要点,理解了线程同步、线程安全等问题,也增加了多线程调试的经验。
线程安全方面,我在本单元的作业中并没有遇到线程不安全的问题,应该是因为我采用的设计比较保守且简单粗暴。层次化设计方面,个人的经验主要是在需求容器的设计上,将需求进行恰到好处的分解,的确可以极大地方便电梯的调度,而不合理的容器设计则会给调度带来很大的麻烦。调度策略上,如荣老师课上所说,不存在所谓最优的调度策略,合理即可。而我采用的非常简单的Look+自由竞争策略在强测中也有着还不错的性能表现。