BUAA_OO_第二单元作业总结

BUAA_OO_第二单元作业总结

第一次作业

同步块的设置和锁的选择

本次作业中只有两个线程,电梯线程和输入线程,调度器作为一个共享对象。

由于本次作业比较简单,因此只在电梯线程和输入线程中对调度器的相关属性进行访问和修改使用了synchronized锁。

调度器设计

第一次作业中并没有将调度器作为线程,只是将其作为一个共享对象,用来存储等待队列,给其配备了添加请求和取出请求等方法,在输入线程和电梯线程中添加调度器属性,通过调用调度器的相应方法来实现调度器与线程之间的交互。

对于Random模式,调度方法采用了SSTF算法,即当电梯内有乘客时其正常运行,如有同方向乘客且电梯内部乘客未满,则将其捎带至电梯上;当电梯内无乘客时,电梯前往与当前所处楼层最近的乘客请求。

对于Night模式,采用了LOOK算法,即当前电梯运动方向前方有请求时则继续向前,若前方无请求则转向。

对于Morning模式,由于当时对于多线程编程还是很陌生极大程度上还是因为懒,故没有采取等人策略,调度算法与Random一致。

架构设计

UML图

UML顺序图

第二次作业

同步块的设置和锁的选择

本次作业中参考了课上实验的代码,设立了三个线程,分别为电梯线程,输入线程和调度器线程。

同时,设立了三个类,分别为总的等待队列,电梯的等待队列,电梯的乘客列表,使用synchronized关键字对上述三个类的相关方法进行了修饰,将其设置为线程安全类。

因为在三个线程中,有些方法需要将上述三个类中容器的队列取出后做具体的访问修改操作,单单将这三个类设置为线程安全类不足以保证线程安全。因此,需要在各个线程中对容器进行访问的部分使用synchronized关键字加锁。

调度器设计

第二次作业中将调度器作为一个单独的线程,其内设置了总等待队列与电梯的等待队列两个属性。

对于Random与Night模式,当总等待队列中有新的请求时,直接将总等待队列中的新请求分配到各个电梯的等待队列中,使得每个电梯中等待队列的人数相对平均。

对于Morning模式,采取了等人方法,即当有新请求时,将其加入当前要分配电梯的等待队列,当当前电梯等待队列人数达到最大限乘人数时,将该电梯线程唤醒,然后循环指向下一个电梯。

对于单个电梯而言,对于三种模式其处理都是相同的,根据电梯自带的等待队列采取LOOK算法进行工作。

架构设计

UML图

UML顺序图

第三次作业

同步块的设置和锁的选择

由于在第二次作业中考虑到了第三次作业扩展性,锁和同步块的设置与第二次作业几乎完全相同,故在此不再赘述。

调度器设计

第三次作业中架构设计同第二次相同。

对于Random模式,采取了换乘策略,用百分百面向过程的方式写了一个换乘策略。

大概思路是,因为每次人进行换乘时都需要先下电梯再上电梯,以最理想情况计算,其也需要在当前楼层停靠0.6s的时间,考虑到这个时间开销,因此我大胆决定对于每个乘客其至多只能换乘一次。在该种情况下,考虑其从某一层到某一层所需要的最短可能时间(也即最短路径,当然也要考虑到部分节点换乘的开销)。然而由于笔者前期考虑情况过于简单,错误的估计了如果单纯用分支判断可能会有的代码量,所以仅换乘策略就写了长达200+行。

虽然对于Random模式的大部分点都能取到极高的分数,但是有两个点没有得到任何性能分。在那一刻,我突然意识到,对于我的电梯,或许不换乘才是他最好的归宿。

对于Morning与Night两种模式,由于其起始楼层或目标楼层比较特殊,综合考量下没有采用换乘策略。但在这两种情况下,当有新电梯加入时,其会将所有未处理的请求全部放回到总等待队列中,进行一次重新分配。

事实证明,对于通过的点来说,这样的处理是可以得到很高的分数。但由于吐出请求时处理不当,导致两个点RTLE了,对于bug具体原因会在下面具体阐述,此处就不再赘述。

架构设计

UML图

UML顺序图

可拓展性分析

本次作业的架构扩展性还是比较好的,除去写的很糟糕的换乘策略,其余部分的功能扩展都很容易实现。

如对于电梯属性的修改,本程序可以通过改变ElevatorIn类中的内容以及相应的实例化对象的数量实现种类更多数量更多的电梯;想要增加更加复杂的电梯楼层限制,只需要修改Scheduler中的换乘策略即可。

除去Scheduler中十分臃肿的换乘策略,出去换成策略之后,其余部分的代码量分布相对均衡,基本做到了高内聚低耦合的要求。

自己程序的bug

第一次作业中在公测和互测中均未出现bug。

第二次作业中在公测中没有出现bug,在互测中被hack了一次。

经分析,该bug出现的原因是因为在对线程安全类中的容器进行循环遍历时未使用synchronized关键字加锁导致在某些执行顺序下会出现线程安全问题。

第三次作业中在互测中没有出现bug,公测中出现了两个bug。

经分析,这两个bug出现的原因是相同的,都是因为在电梯加入新电梯时,将所有电梯中未处理的请求吐出的过程中线程安全问题没有考虑好,导致在某些执行顺序下可能会出现线程安全问题。

自己发现别人程序bug所采用的策略

依旧采用黑箱测试,通过随机生成样例进行并手动构造一些极端数据进行评测。

前两次作业没有发现任何bug。但在第三次作业中由于个人线程安全问题没考虑好,进了B房间,所以相对容易的找出了3个bug。

经分析发现,有1个bug是因为其在换乘时没有处理好,导致在某些执行顺序下可能会出现B电梯到偶数层的情况;还有1个是因为线程加锁过多,导致只有部分请求全部处理完之后才会读入新的请求,进而导致其运行时间极长;最后1个比较玄幻,其电梯在某些指令序列下会出现关不上门的情况。

心得体会

第二单元的作业给我的最直观感受就是代码写起来很快,但是debug是真的很慢。

由于synchronized关键字所处位置不合适,很容易就会出现死锁和线程不安全问题。

对于死锁问题虽然不容易改,但其大多数情况下还是稳定复现的,可以有一个相对明确的修改方向。

但对于线程不安全问题,由于多线程问题的不可复现性,导致笔者在使用一个测试样例测试出bug之后,需要反复测试很多次才有几率复现bug,继而进行修复,这是一个极其漫长的过程。

通过这一单元漫长的修bug经历,让我对线程安全这一问题有了更深刻的理解,要想保证线程安全,一定要确保锁的位置合适,同时,临界区的大小也要设置的得当;其次就是层次化设计,虽然本单元作业依旧进行了一个小的重构,不过相比于第一单元作业那种大篇幅的修改,已经有了一定的进步,作业的可扩展性相比于上单元的作业也提升了很多。

posted @ 2021-04-25 15:57  Hankitle  阅读(67)  评论(2编辑  收藏  举报