OO第二单元总结

OO第二单元总结

第一次作业

本次作业的需求是模拟单部多线程实时电梯,我采取的是生产者-消费者模式,用共享对象连接各线程。

调度策略

对于本次作业我针对三种模式设计了相应的调度方法。

  • Night模式:

    由于Night模式所有乘客同时到达,所以电梯激活后先sleep一小段时间保证所有乘客需求进入等待队列中,然后将乘客按照起始楼层编号进行降序排序,到达该楼层接该名乘客,在回一楼的时候过程中若有楼层有乘客则捎带,直到满人。

  • Morning模式:

    读取到一个乘客需求后等待2s,由于两个请求间隔不超过2s,等待2s后至少会有两个请求(一般能捡3、4个),然后将当前等待队列乘客目标楼层按降序排列,优先送高层,这样来回的时间足够久,可以保证每次回到一楼基本都能载满人。

  • Random模式:

    采取指导书中的ALS策略

UML类图

 

整体架构是仿造training写的,Request类是为了后续拓展而自己在官方包类的基础少稍作修改的乘客请求类,共有三个线程:输入线程(InputThread)、电梯线程(Elevator)、调度线程(Scheduler),输入线程和调度线程间用waitQueue连接,记录当前等待队列需求;调度线程和电梯线程间用ElevatorQueue连接,记录电梯状态和当前已分配需求。实际上,由于本次作业只有一部电梯,是可以不需要调度器的,但是为了后续作业迭代的便利加上了调度器(其实后面也没用到)。

同步块的设置和锁的选择

对于共享数据Request采用当可能对其发生修改时将其锁住的方法。

BUG分析

使用的ALS,电梯没有乘客时加入主请求后前往该主请求过程中没有进行捎带,直接到达其起始层,导致一些点T了。

hack策略

主要采取构造边界数据的策略进行hack。比如Night模式下加入若干个20-1的请求或者Morning模式下定时加入1-20的请求。

互测过程中hack到的基本都是线程安全问题,无法将电梯停下来或者有的请求被忽略了。

第二次作业

本次作业需要模拟多部同型号电梯的言行,并且能够动态增加电梯。整体架构与上次类似,就是多了两个消费者,变成了多消费者模式。

调度策略

不同电梯间共享一个需求队列,由Schedule获取需求后分发给每个电梯对应的ElevatorQueue,各电梯进行自由竞争。

对于每部电梯,Night和Morning采取策略与第一次作业相同,Random模式下改成了look算法。

UML类图

 

可以发现,本次架构较第一次相比几乎没有变化,只是将建立调度线程和电梯线程的步骤放在输入线程之中,然后将Request改了个名(因为和官方包重名了),MyRequset就是我自己的乘客需求类,为了实现look需要给每个需求一个flag,当flag为true时代表改乘客已经被接走从而让look可以通过判断同向楼层中是否有flag为false的需求进而判断是否掉头。并且这次在每个电梯对应的ElevatorQueue中加入二维列表,外层共20项,表示20层,内层存储相应层的乘客需求,也是为了实现look。本次作业较上次作业的修改主要在于将Random模式下的捎带策略进行修改,其余部分基本没做什么修改。

调度器与程序中线程的交互

由于我采用的是自由竞争,调度器形同虚设..主要用于将WaitQueue中的请求转移至ElevatorQueue中

BUG分析

改用look后在1层和20层的掉头判断有锅,导致一部电梯运完乘客刚好在这两层,同时其他电梯刚好运送完所有乘客的情况下,这部电梯会卡死循环无法终止进程,稍微加个特判即可。

并且在强测数据公布之后我发现Night有个点在1s中给出了所有请求,在10s加入了新电梯,但是我发现我并没有用到这台新电梯,最终分数也是可观的(是否说明很多同学都没用到这台电梯?),究其原因,我发现是电梯的结束线程的判断有误,原来是该电梯乘客列表为空、WaitQueue乘客列表为空且输入结束就终止线程,如此便会出现一台新电梯刚进来的时候是没有给他分配乘客需求的,而输入已经终止,WaitQueue也已供给给了每个电梯,导致新电梯直接结束。修改方式在第三次作业架构设计中体现。

hack策略

同样采取构造边界数据的策略进行hack,如Morning模式下每隔2s才输入一个从1-20的请求,Random模式下不停地分别加入1-20、20-1层的请求。

互测只hack到一个点,似乎是因为最后几个请求都分配给了一部电梯,而其他两部电梯忙等从而CTLE了。

第三次作业

本次作业需要模拟多部不同型号电梯的运行,型号不同,其移动速度、限载人数、可停靠楼层皆不同。仍然采用的是生产者-消费者模式。

调度策略

本次作业仍然采用自由竞争,但是将三种模式的处理方式都并为look了。这次作业重要的点是BC类电梯只能到达指定楼层,所以为了充分利用各电梯,需要进行换乘处理。我的换成策略就是A类电梯正常look;B类电梯就在奇数层look,若接到的乘客目标楼层为偶数,则将他运送至目标楼层的低一层;C类电梯就在1-3、18-20,若在1-3层往上走时look的捎带准则为目标楼层大于等于12,并且若目标楼层不在18-20内便将其送至19楼以便换乘A或B,在18-20层往下走时的捎带准则为目标楼层小于等于8,并且若目标楼层不在1-3内便将其送至3楼以便换乘。(其中的12和8是在本地用上次强测代码多次测试效果较好的参数)

UML顺序图

 

UML类图

 

可以看到,整体架构与第二次几乎一样,只是为了满足换乘在每个Elevator中拥有了一个当前所有电梯的状态表,每当电梯将一需要换乘的乘客送到换乘楼层时唤醒一下当前不动的电梯,但显然这样会使得Elevator类过于臃肿,并且各个电梯间耦合性过高,违背了高内聚低耦合的原则(为了图方便就没细管)。同时,我的乘客需求类又需要增加一个arrive,用以判断当前需求对应的乘客是否已经到达他最终的目标楼层进而判断是否需要终止电梯,look掉头判断还是借助flag判断。并且,为了修改第二次作业中提到的bug,增加了Floor楼层类,让每部电梯都拥有同一个Floor类对象,使得每台电梯对于乘客当前分布状态是一致的,然后将电梯终止的判断添加了一条,所有楼层的乘客都到达他的目标楼层时才终止。

虽然针对第三次作业设计的调度策略在强测下表现不错,但是,我这样的策略是不具有拓展性并且有极大的局限性的,若随便修改下电梯的可到楼层我便需要推倒重来,或者是用一些较为极端的数据我就只能用A类电梯慢慢跑。在互测阶段看到一些同学利用最短路径等算法来进行调度,我觉得这样的方式更具有拓展性,且针对面更广,非常值得学习!

BUG分析

在本地测出了一个BUG,手贱关掉了,后续尝试很久没再能复现出来,很可惜。

hack策略

本次hack策略为构造极端数据,如让若干乘客从4-20,能hack到同组一位同学RTLE,但奈何我自己的电梯不能跑进70s,数据非法。

似乎到第二单元同学们都没有第一单元hack那么积极,每次同屋基本就一两个人在刀人。第二单元互测给我的感受就是有点玄学,有些bug本地hack的是A,交上去把B给刀了,然后自己再测B又复现不出来,我想如果能够手动控制线程的优先级或许一些bug会更容易复现一些。

心得体会

线程安全

  • 电梯线程终止的条件需要严谨思考,轻则像我一样一些情况下新电梯直接终止,重则电梯无法停止,造成的后果是难以想象的。

  • 线程安全其实可以只由线程安全类集中管理,像我那样到处加锁会显得特别繁琐并且缺乏阅读性,集中起来管理更加清晰。

层次化设计

虽然说三次作业的迭代上写起来感觉还行,不需要改很多内容,但是现在写总结再回去看代码还是感到略微不适。Elevator类过于臃肿并且和ElevatorQueue有极大的重合性,并且前两次作业中针对不同模式设计的不同调度策略其实绝大部分代码是类似的,应该抽象出一个策略类,让电梯针对不同模式调用不同策略,分担复杂度,也增加了代码的可读性,这样后续作业的迭代基本只用对策略类进行修改。

posted @ 2021-04-25 15:26  Sharpzz  阅读(80)  评论(1)    收藏  举报