北航面向对象2022第二单元总结(Elevator.exe已停止运行...)

您的电梯已停止运行(NO!)

这里是BUAAOO第二次单元作业总结博客。第二单元是BUAA的电梯模型,今年的电梯比起往年看起来友好很多。在第五次作业上手时,因为是第一次搭建相关逻辑,并且对于多线程构造和运行状态处于懵懂阶段,可能会遇到一些麻烦。(看到往年第一次的作业是傻瓜电梯的时候,我还在想今年的电梯上手真困难)不过在第六、七次作业中,一些增量迭代显得不那么困难,仅仅只是从单纯的纵向电梯转为了横纵交错的电梯,最后实现了可换乘的电梯运行mode。总而言之,三周的作业写的十分充实,并且让我感受到了多线程别致的“魅力”。


HW_5

本人的三次电梯架构基本完全基于第五次作业的base,即使增量也没有进行大规模的重构。因此在这里,我先进行电梯整体架构设计的阐述。

同步块选择及锁的设计

首先是同步块的设置和选择,在没有参考实验代码时,我尝试自主设计同步块和有关锁,然后在自信满满地上交后,发现电梯光荣地死锁了。之后,我好好研究了一下第三次实验的代码,发现一个很有意思的事情:所有的操作线程wait和notify的内容都放在了RequestQueue中,在这样的设计下,同步块和锁集中于此,各个线程相当于把这里作为了一个公共空间,在想要访问该内容时便采取“等待”、“进入”、“使用完让行”的操作。这样的设计可以很好地满足线程安全的原则,并且也能解决我的设计问题。因此,我将每个电梯内的服务队列所在类设置为同步块集中区,并且使用synchronized关键字进行互斥维护,此时,对每个电梯来说,线程安全就得以满足。

调度器设计

在任务分配,也就是调度器方面,我直接用主线程来进行任务的输入操作,并没有单独的设置一个新的线程来进行处理。任务的处理以及调度的策略集中存放在TaskTower这个类内,用任务塔作为一个中介,将任务由输入线程转入电梯运行线程。

整体框架

首先是hw5的UML类图:

由于第二单元刚开始,大家提到生产者-消费者模式比较多,因此我也对应着做了一点调整。具体来说,输入作为生产Passenger,也就是任务的生产者;电梯内Strategy存有待做任务和正在做的任务,对应两个队列,电梯运行时会获取内部的Passenger加以送达,也就是任务的消费者。具体到电梯内部运行,和标准流程一样,用ALS策略,在上升下降的过程中,如果电梯没有人,就指定在本电梯外部等待的第一个Passenger为主任务对象,以它为目标。如果内部存在任务,就更换主任务对象为内部第一个存入的任务。

程序BUG

本次在公测互测中分别出现了一个bug,一个对应线程永久等待不会结束的问题,导致最后rtle。主要原因是在输入线程结束,setEnd位时,没有处理好互斥原则,导致设置End位在等待以前结束,而唤醒结束后才进入wait。另一个对应输出线程不安全的问题,导致时间戳获取与实际输出顺序颠倒。

由于第一次互测经历了重测,我在互测中总共发现了其他人的两个bug,刚好对应我自己的两个问题(同组人没想到也犯了一样的错误)。在互测中,我自己使用数据生成器生成数据来进行hack,不好构造数据时,压力测试还是很好的选择,毕竟没有重测之前,本组就只有我测出了一个bug。


HW_6

第六次作业主要增加了在楼座间可以循环运行的横向电梯,以及可以新增电梯的操作,需要采用新的设计思路来管理。但总体上来说,可以和hw5采用一样的方法来处理。

调度器增量

由于我们需要满足新的请求:增加电梯请求。因此,我们要在调度方面采取一定的策略来选择新加入的电梯。我并没有采用电梯自由竞争的方式,而是按照课程组给出的标程,直接顺序循环遍历可以处理任务的电梯,保证均匀分配。事实证明,在强侧中我使用标程也取得了很不错的效果,不仅完全不存在超时的问题,在个别任务上还达到几乎满分的速度。

框架升级

接下来是hw6的UML类图:

可以发现,我改进了电梯管理类来方便新增的电梯管理。这里主要谈一谈新增横向电梯的设计策略。总的来说,从UML图中也可以看出,我设计时基本参照了纵向电梯的设计思路。主要区别在于,横向电梯把顺时针看作上升,把逆时针看作下降。同时,用起始楼座之差的绝对值和符号来判断向哪个方向走最近。具体如下:

boolean upOrDown =  !(endID - stID > 2 || (endID - stID < 0 && endID - stID > -3));

程序BUG

本次在公测互测中没有出现bug,本次测试中,同组的大家基本都考虑到了第一次出现的各种线程安全问题,并且在线程结束方面也下了狠手。并且新加电梯的个数有限,不太容易发现bug。我还是采用随机生成数据的方式来进行hack。但是我采用了“新的实践方式”,我把自己前几版错误的代码拿出来进行错误代码认证,并且将产生错误的生成数据归纳到对应的bug下。果然“实践是检验真理的唯一标准”。


HW_7

第七次作业主要增加了电梯可设置参数的要求,以及乘客可以跨楼座跨楼层移动的请求。实际上前者在我们的设计中很好体现,只需要添加对应的参数输入即可。后者我虽然参考了课程组给出的流水线代码,但是实际上做出来是一个分段式的三段请求链结构,分成纵向—横向—纵向来进行任务运行。

调度器改进

由于我们需要满足电梯换乘,在我的设计中,我在调度器中增加了换乘逻辑。具体来说,我将请求分为两部分,一部分对应直上直下的固定请求(hw5),一部分对应跨楼座的请求。对于后者,每当有新任务进入调度器,就分析在何处跨楼层,并让乘客记住自己将换乘的电梯及楼层,并进行任务投喂。(我不是采用的电梯获取请求,而是调度器投喂请求的方式,也就是不会自由竞争,单纯进行均衡分配)

框架进一步升级

接下来是hw7的UML类图:

可以发现,我改进了电梯管理类来方便换乘时的电梯管理,并且增加了一个单例对象来方便换乘。事实上,对于乘客来说,如果需要换乘,他总共会有三个运动阶段,在每一次被投喂给电梯前,我会先更新乘客的运行状态,修改好起始、终止楼层,起始、终止楼座。因此,当所有阶段完成后,乘客就会被移除任务清单。

另外值得一提的是,我借鉴了实验exp4_2中的请求清算结束方案,在输入线程结束输入任务后,被视为新的结算线程运行。每当有乘客请求投入时,RequestNum自增1,每当类似上述所说的乘客被移除任务清单后,触发任务结算,并将RequestNum自减1,最后,所有任务都被处理完,并且输入的线程显然也消亡了,就会触发整体模块的结束,顺利退出程序。

我们把最后设计好的情况的四个时序图展示如下,分别对应主线程(启动线程)、输入调度、以及横、纵电梯的运行逻辑,最后是请求结算逻辑。

程序BUG

本次在公测互测中出现1个bug,但我自己在之后又整理出一个bug.前者是致命性bug,由于单元测试不充分,发现只要来同座运输请求(hw5),就会由于强行调用第二部分的方法导致空指针异常(很心痛,很值得反省)。后者是由于换乘导致的线程互斥问题,主要是对于迭代器遍历同时修改,造成了不可知的影响。

在互测中,我针对代码运行逻辑试图卡运行时间来hack,构造了边界情况:在输出截止时间输入最大数据量最远抵达路径的乘客。成功造成了两次hack;另一方面,和hw6一样针对自己的代码生成了错误数据代码,又造成了两次hack。最后总计造成了4次不同的逻辑hack,有比较好的效果。


心得体会

通过本单元学习,可以感受到助教们的满分热情(bushi)。我们在第一次作业就直接采用ALS策略电梯,构建起对多线程的整体框架认识,快速上手。后面两次作业看似加了不少新内容,但做起来也并不复杂,只要有了第一次作业迈上台阶的基础,就能很好地处理多线程中的各种问题了。(被hw5暴露的各种bug很好地让我认识了多线程中可能出现的各种问题)

另外,我本人不太擅长自己构建一些新鲜的策略算法来进行处理,于是按照课程组给出的标程逻辑一步一个脚印走完了全程。总的来说,由于多线程调度的不确定性,我使用标程的代码最终取得的效果并不赖,完全不存在超时的问题,也从未产生过轮询的现象,没有出错的得分点分数也不错,这让我感到一丝丝自己完成一份任务的成就感。

撰写本总结博客时,回味整个月的思考、迷茫、再建、讨论、测试、修复,真是百感交集。留下的,不只有经验教训,更有继续勉励的信心。

精感石没羽,岂云惮险艰。”OO课程已过半,后面就,再接再励吧。

posted @ 2022-04-27 15:22  2037hanzhe  阅读(42)  评论(1编辑  收藏  举报