2022_BUAA_OO 第二单元总结

2022_BUAA_OO 第二单元总结

同步块的设置

我实现本单元的三次作业的架构较为相似,主体均为生产者消费者模式,在该模式的实现中,介于生产者和消费者之间的托盘类是唯一的共享资源,故同步块只需设置在对这些资源进行访问的代码区域。在我的具体实现中设置了RequestTable类来存放请求,并将其作为锁对象,使用synchronize锁,将对其进行添加、删除、遍历、等待操作的代码区域设置为同步块。同时在对信号量的查收方法上用单例作为锁来保证线程安全。

同时为了尽可能缩小同步块大小以及削弱加锁对于程序并发性的影响,我用HashMap<HashMap>结构来分别维护横纵向电梯候乘表,这样加锁影响到的线程数量大大减少,提高了程序的并发性。

调度器设计

三次作业的调度器设计上都主要考虑单个电梯应对请求的设计,而没有专门设计对于所有电梯线程的中央调度器,对于同一层或同一座的请求都简单地采用了自由竞争的调度方式。

具体到对于横纵电梯的调度均采用了LOOK策略。基本逻辑为在某一方向运行时,若当前位置有请求,且请求的运行方向与当前相同,则开门接收乘客。若电梯内没有乘客,且前方没有新请求且该位置的请求与电梯运行方向不同,则转向,只是横向电梯的请求方向需要另外判断其为顺时针或逆时针,且将被唤醒后的第一个请求的方向作为到下一次wait前的运行方向,即尽量减少换向次数。

对于第七次作业的换乘过程我将其拆分为“纵向-横向-纵向”三个过程来进行处理,考虑到换乘要等待的时间开销较大,同时使用多次换乘能减少运行时间的情况较极端,我的实现中最多只乘一次纵向电梯,这样的设计下,对于每一段运行,只需将请求的目的地设置为其下次换乘的实际目的地,在每次下电梯后检查,如果该请求的出发楼层/座与目的楼层/座均不相同,将请求的出发出发楼层/座更新为当前的楼层/座再重新进行分发。

架构设计

第一次作业

uml图如下:

采用生产者消费者模式,将RequestTable作为中间的托盘,对每一座的电梯设置一个请求表,在Input线程里完成分发,同时考虑到可扩展性,我尽力将电梯架空,内置一个调度器,电梯只知当前位置,主要工作由调度器完成,这样在后续作业中都只需要修改调度器,而电梯线程的代码可以很好地复用。

同时为了保证输出的时间戳递增,设置了一个Output单例。

第二次作业

对于同座/层的多部电梯采用自由竞争的方式,新增电梯的过程在Input线程中实现,较上次作业的修改只在于新增了调度器接口实现类工厂模式以及横向电梯的调度器类。

第三次作业

这次作业由于涉及到换乘,需要用到将请求从电梯线程取出重新分发的方法,受课上实验启发,我借鉴了流水线模式,设置了Controller和RequestCounter两个单例,在Controller类里实现新增电梯线程、对于请求的解析、分发以及确定中转楼层、分发结束信号等工作,RequestCounter则负责对于乘客请求的查收,以产生结束信号。uml图如下:

这次作业的一个设计重点是对于中转楼层的选择,由于变量过多,同时动态的规划较难实现,我选择设置了一个简单的静态代价函数来获取预期的最小时间代价,主要考虑运行时间为每一段最短运行时间的加和,而对等待时间的刻画十分粗略(这问题复杂,怕反向优化,是在下摆了 (> _ <))。

时序图

考虑到第三次作业覆盖了前两次作业的功能,可以使用第三次作业的时序图表示该单元的线程间协作关系。

协作图如下:

BUG分析

自己的BUG

第一次作业互测中由于没有保证输出的时间戳递增而被hack。

第二次作业在互测中发现我的横向电梯调度策略有bug,原来判断是否转向是看在该方向上接下来的两个楼座有无同方向请求,而假如该电梯在A座,B座新增一个逆时针方向请求,同时E座新增一个顺时针方向请求,这种情况下就会陷入死循环,修改后的策略是在尽量减少换向,只在电梯wait被唤醒后由第一个乘客的请求方向来决定电梯接下来的运行方向,考虑到只有五个楼座且是循环的,这种方案不易出错且易于优化。

第三次作业实惨,横向电梯接乘客时可达性的判断条件没写全,然后强测就wa声一片,又是虎头蛇尾的一个单元。。。

同学的bug

第一次作业中,检出有同学的电梯可能会超载;

第三次作业中,有同学和我一样对于可达性判断条件写错了,陷入轮询。

心得体会

SOLID原则

受单一责任原则思想的指引,本单元作业的设计中我都有意地确保每个类和方法完成的任务相对单一。线程类的run方法中,也主要是做了对于线程是否结束的判断,而在实现功能上调用下层的private方法,以使得各个方法的逻辑清晰而易于梳理。

线程安全

本单元的作业的实现过程中我在控制线程结束的设计上花费了较多精力。由于结束条件逐渐复杂,若没有考虑完全,很容易出现电梯线程陷入轮询或者提前结束的尴尬情况,好在实验课上学到了设置单例来查收请求的方法,实现起来不再需要多层之间冗杂的条件判断和信号传递。

层次化设计

将电梯架空以及调度器接口的设置体现了SOLID中的开闭原则,很好地实现了代码的复用,使得三次作业间的迭代十分顺畅。

posted @ 2022-05-01 22:41  luiluizi  阅读(40)  评论(2编辑  收藏  举报