OO第二单元总结

OO第二单元总结

本单元的作业总体来说比较愉快,毕竟不像上次一样次次重构。
本单元为电梯系列问题,涉及到多线程问题。简单起见,我使用的是生产者-消费者模式。

第一次作业

本次作业要求实现单部可稍带电梯。看完题目后我认为生产者-消费者模式非常适合解决这个问题。
本次电梯我采用的是look方法。本方法核心即在于电梯方向的判断,这在Dispatcher中的rstJudge()方法有体现。具体说来,电梯会一路捎带当前运行方向上(当然,要在电梯暂时不反向的情况下可以到达才行)的所有请求。若没有找到这样的请求,才会去注意反方向的请求。

Dispatcher:调度器类,也即托盘类,可获取队列中的请求以及分配请求给电梯,添加请求和分配请求方法均上锁。
Elevator:电梯类,也即消费者
Main:主类,也没啥好说的,不过它也被用来干一些奇怪的事(比如存储全局变量之类的)
InputDeal: 管理输入,主要从输入中读取请求,也即生产者
RequestQueue: 用于存储请求的队列,并定义了一些相关的操作。另外,该类是一个单例,只有Dispatcher能对其进行操作。
类图

其时序图大致如下

度量分析如下


可以看出核心方法的复杂度过高。
分析
本次作业有一个最大的问题即是如何结束线程。我采用了一个全局变量来标记输入是否结束,当且仅当输入结束而且请求队列和电梯内均为空时,电梯线程才会退出。
Bug:
本次强测和互测均未发现Bug,互测时纯随机测数据,也未发现他人Bug。

第二次作业

本次作业加入了新的电梯,并且加入了乘客数限制,相对的,正确性判断中的性能则不再与某一策略的时间进行比较,应该说是放宽了。
在不考虑性能的情况下,这次作业极为简单,仅仅对第一次的代码进行少量修改即可完成。本次我基本上沿用了第一次的策略。其时序图与第一次基本一致,类图和度量分析如下



问题基本上同第一次作业,值得注意的是这次Elevator.run()为红,原因在后面解释。
分析
这次本打算考虑如何能有更好的性能,但是我想到的任何可以不是很复杂地实现的方法基本都有两面性,于是造成了我的选择困难,最后干脆就直接沿用了第一次的调度。具体来说就是每个电梯独立地按照第一次作业中所定义的行为行动。唯一的区别即在于某个电梯超载时就会无视任何请求。由于我的电梯并没有预分配请求,而是在每一层来判断请求位置并随之移动,这也就导致了这些电梯行为模式几乎一样,会几个电梯一同去接某一请求,但只有一个电梯能接上。显然这样会使性能有很大的下降(但是超载情况下性能会爆发式增长)。经过试验,我发现在创建电梯的时候让它们相互错开一段距离(这就是run()方法飙红的原因)可以部分破坏它们的同步,大多数情况下性能会有一些提升。
然后,理所当然的,我这次性能基本就取决于是否超载了,但是最后强测得分居然还能看
Bug
强测未发现Bug,互测也是随机方式,未发现他人Bug,然而自己却被HACK了一次。于是,玄学Bug出现了。我在本地测试无法复现,而且我十分确认我把应该加锁的地方都加锁了并且在应该唤醒的地方都唤醒了。从输出来看,评测的时候我并没有产生死锁,更像是一些出现概率极低的逻辑错误。由于我沿用了第一次的代码,而第一次并未出错(甚至第三次沿用了第二次且第三次也未出错),我一度怀疑甚至是评测机的锅。当然,最后我也没能找出Bug,原封不动地重交了一遍后过了。

第三次作业

本次作业加入了可到达楼层与电梯种类,同时可以动态增加电梯。为了实现动态增加,除开始的三个电梯外,改为由Dispatcher来创建电梯。除此之外基本沿用第二次的策略,除前所述以及结束外,时序图基本与第一次一致。类图和度量分析如下




可以看到复杂度与第二次基本一致,但是有些方法由于需要一些额外的判断,复杂度不可避免地会上升。
分析
本次作业为了保证正确性而并未过多考虑性能。总体仍然采用look模式。为简化换乘,我指定了1楼和15楼为换乘地点,需要换乘时会就近选择换乘点。每个电梯的逻辑都是相同的,仍然是自由竞争的模式。但是每个电梯只会在意自己能够直达或是没有任何电梯能够直达的请求。在换乘时请求会由电梯来抛出,加入到请求队列中(为保证同步,此过程由Dispatcher进行)。本次作业最大的问题之一也是如何判断停止。采用前一次的判断方法显然是不行的,因为会出现需要换乘的情况。只有当输入结束、请求队列空、所有电梯均空的时候才能结束。
个人认为设计主要是未满足SRP原则,部分类(主要是调度器和请求队列之间)的功能有些许交叉。其他如开闭原则,由于基本调度全由调度器实现而且分工明确,扩展并无需大幅修改;由于本次代码中关系简单且并无继承,也仅有Runnable接口,故运用其他3个原则进行分析我认为意义不大。如果说不考虑性能,那么我自认为本次作业还是有一定的扩展性的。但考虑性能的话就不能看了。
Bug:
强测和互测均未被发现Bug。互测中我想到如果我在加电梯后不加人很可能会导致一些粗心的小朋友死锁,于是交了一个这样的数据成功地让一个人死锁了。另外由于这样操作的话最后一条调度时间与最后一条输入时间无关,于是我尝试性地交了一个215s加电梯的数据,本意是想坑一下那种等到输入全部结束后才开始一波带走的小朋友们,然后由于评测时根本不管最后一条调度的时间而是运行时间超时直接判断TLE,结果出现了出乎意料的1穿7场景,虽然后来理所当然地被清掉了,但是看到的一瞬间觉得真的爽啊

总结

本单元作业让我体会到了与以前完全不同的一个世界。在多线程的世界里,无论是代码写法、架构设计还是debug方法都与单线程有着极大的区别。考虑死锁、同步、阻塞等问题也是乐趣无穷。在这里,我也学到了很多设计模式并领略了它们的魅力。同时本单元的三次作业让我经历了一次完整的迭代开发而不是重构,让我对自己的设计能力更有信心了。

posted @ 2020-04-14 22:40  6yyh6  阅读(164)  评论(0编辑  收藏  举报