北航OO第二单元总结

一、架构分析

1.1 第一次作业

类图及分析

Elevator:用于处理电梯的各种行为,包括工作,开门,关门,人的进入与离开。

LoadList:按照不同的目的地保存电梯内的人。

WaitingList:按照不同请求位置保存等电梯的人,并以此判断电梯该如何运行。

Scheduler:保存六部电梯的信息并将请求分配给对应的电梯。

思路

1、Scheduler用于读入请求并将请求分配给对应的电梯,每部电梯的请求是利用ArrayList<>[]按照请求所在的楼层存储的。我一开始没有多想,按照习惯将数组大小设置成11,也就是说,数组的0~10对应电梯楼层的1~11。结果写着写着就发现,很多地方都要+1或者-1,出了很多错误。后来我将数组大小改成12,数组的位置对应的就是电梯的楼层,果然要方便很多。由此可见,有些时候还是要变通一点。

2、电梯具有curPosition和targrtPosition两个属性,当电梯的目标位置高于当前位置时,电梯向上运动,当电梯的目标位置低于当前位置时,电梯向下运行。当电梯的目标位置等于当前位置,也就是电梯静止时,假设存在上方的请求,就让电梯向上运行,否则电梯向下运行。

3、电梯上行或下行的时候,每到一层,会判断是否有要上电梯的人以及电梯是否满员。如果有人要上电梯且未满员,就让电梯停下来,然后按照目的地由近到远的顺序让人上电梯,之后更新电梯的目标位置为电梯中的人所要去到的最远位置。

优点:策略简单,方便实现。

缺点:性能可能会差一些。

这次加锁的思路并不复杂,我只是在每次WaitingList的request发生变化前后加了锁,防止多个线程同时对WaitingList进行修改。

类复杂度分析

方法复杂度分析

1.2 第二次作业

类图及分析

这次和上次一样,仍然是有五个类,但每个类和上次比都有了一些变化。

Elevator:和上次作业相比,增加了处理reset的方法,包括替换速度和容量、让所有人下电梯等。

LoadList:和上次作业相比,增加了让所有人下电梯且更新请求的方法。

WaitingList:上次作业,每部电梯都有一个各自的小WaitingList,而这次作业,由于没有指定电梯,我就只设置了一个大WaitingList,把全部的请求都放在一起。

Scheduler:上次作业,Scheduler将请求分配给对应的WaitingList,这次,由于只有一个WaitingList,Scheduler直接放入请求。

思路

和上次作业相比,这次作业有两个变化,不再有指定电梯和增加了reset。

这次关于电梯运行,我的思路是,当电梯没有满员时,会一直顺路捎上沿途的人。 当电梯由停止启动时,会找到离它最近的请求并将请求设置成startTarget,并且将电梯的targetPosition设置成请求发生的位置,然后电梯向请求的方向移动。

关于reset,思路并不难。要将电梯的速度和容量替换成新的值,还要将电梯的LoadList清空,用curPosition替换LoadList请求中的fromFloor,组成新的请求,放回WaitingList。但是我觉得这里要考虑的细节很多,稍不留神就会出错。在此,我简要列举几个我错过以及差点出错的注意点:

1、接到reset指令时,不能直接替换电梯的速度和容量,因为电梯此时可能还在运行。我设置了两个中间变量,等到电梯里的人全部离开后再进行的替换。

2、接到reset指令时,电梯可能还没接到startTarget,此时需要把它放回WaitingList,其fromFloor是原本的fromFloor,而不是curPosition,并且此时不用输出OUT。

3、假设有人在reset指令时恰好到达目的地,则无需把请求放回WaitingList,否则会输出两次OUT。这里需要进行特判。

4、放走所有人后要将目标位置设置成当前位置,否则电梯会在还没接到人时就启动。

总之,虽然这个单元的作业考的是多线程,但我犯下的和多线程无关的各种奇奇怪怪的错误也不少,可能还是能力有限吧。除此之外,判断电梯什么时候该停下的逻辑也有点复杂,我是想了好几次才终于想明白。

这一次,由于只有一个大WaitingList了,多个电梯会同时从WaitingList中获取请求,同时修改WaitingList。因此我在上次的基础上,在WaitingList中设置了新的锁,加在电梯运行的方法里,防止多个线程同时对WaitingList进行修改。

类复杂度分析

方法复杂度分析

1.3 第三次作业

类图及分析

这次的架构和之前比有了很大的变化,新增了BaseElevator,DCElevator,InnerElevator三个类和IElevator接口。

BaseElevator:一个抽象类,Elevator和InnerElevator的父类,包含了运行,寻找目标,开门,关门,上电梯,下电梯等所有电梯都具备的功能。

DCElevator:双轿厢电梯,负责创建两个InnerElevator和创建线程。

InnerElevator:双轿厢电梯内部的两个电梯。

IElevator:一个接口,Elevator和DCElevator都接在该接口上。

思路

和上次作业相比,这次作业增加了双轿厢电梯。双轿厢电梯在同一电梯井道内同时拥有两个独立的电梯轿厢,每个轿厢只能在特定的范围内运行,并且同一井道内的两轿厢不能同时位于换成楼层。双轿厢电梯的耗电量是普通电梯的四分之一,意味着在一些时候可能应该优先使用单轿厢电梯。

我感觉我可能有点偷懒。我并未考虑耗电量一类的问题,为了省事,直接将双轿厢电梯看成两个具有不同范围的普通电梯。算法也还是和上次作业完全相同。两个电梯不能同时位于换成层这件事,正常方法可能是加锁,而我为了方便,直接将A电梯的范围设置成1层到换乘楼层,将B电梯的范围设置成换乘楼层上面一层到11层。比如,换乘楼层在7层,A电梯的范围是1~7,B电梯的范围是8~11,这样两电梯永远不会相撞。

不过我随即意识到另一个问题,假设所有的电梯换乘楼层都在7层,按照之前的思路就永远无法从7层到8层了。于是我决定,只要出现了相同的换乘楼层,比如已经有电梯换乘楼层在7层了,又有一步电梯要将换成楼层重置为7层,就将A电梯的范围设置成1~6,B电梯的范围设置成7~11。这样,若是六部电梯的换乘楼层都是7层,就会有一部电梯的范围是1~7和8~11,五部电梯的范围是1~6和7~11,可以保证能够处理各种类型的请求。这样性能分可能会差一些,但至少能保证正确。

整次作业,我不知道遇见了多少次CPU超时和总体运行时间超时。原因要么是stop判断错误导致出现死循环,要么是有的电梯一直在wait,一直没有被唤醒。我来来回回修改了好多次都没有改对,十分崩溃。最后想到了一个有点投机取巧的办法——每隔一秒就将所有电梯全部唤醒。这样确实能保证正确。

由于我采用了特殊方法处理电梯相撞的问题,这次作业没有用到新的锁。

类复杂度分析

方法复杂度分析

二、bug分析

2.1 第一次作业

这一次作业没有bug。

2.2 第二次作业

这次作业强测都通过了,但在互测中发现了一个问题。我在reset后才将startTarget请求放回WaitingList,而在startTarget被还回前,又有其他电梯接受了这个startTarget,导致它被接了两次。调整一下代码顺序就好了。

2.3 第三次作业

这一次真的很悲催,强测错太多了,以至于没有进互测。为了找出问题,我加了许多调试语句:

然后我就发现,有几个请求总是莫名其妙就从WaitingList中消失了。我只好一行行检查所有更改WaitingList的操作,边检查边打log,花了很长时间,终于发现是少加了一个判断条件导致有时会多remove。

可能是我比较菜,确实不知道有什么好的多线程debug方法。我以前经常使用的断点和单步两个方法都不能用了,真的很痛苦。感觉只能打印出很多log,多运行几次,看看是卡在哪里,然后检查相关语句看看哪里的逻辑有问题。

三、测试他人程序策略

在第一单元的第三次作业中,我强测没有问题,但在互测时被一个很复杂的数据hack到了。我的程序运行超时了,但是我不知道该如何优化。直到最后我也没能够修复这个bug。但是这件事也给了我一个启发,互测时不一定要检查他人代码是否输出正确,也可以让他们超时。尤其是在多线程作业中,很容易就会出现RTLE的情况。因此,在第一次作业中,我编了一些很复杂的数据,比如在49秒时一下子来了几十条请求。这种方法效果非常显著,我hack成功了8次。在第二次作业中,我让电梯在还没reset完时就来了几十条请求,或是在刚接到请求时就reset,效果依然显著。可惜第三次没进互测,我编的超强数据都没派上用场,有点遗憾。

四、心得体会

恐怖的电梯月果然名不虚传,我本以为第一单元的解析表达式已经很难了,现在发现,那是因为当时还没有经历第二单元。多线程真的好难啊,在写第一次作业时,我不知道锁该加在哪里。第一次提交时,有好几个点都超时了。我最初以为是算法的问题,后来才发现,电梯每次只运了一个人,和预期不符。经历了漫长的排查,我才发现是因为我的锁太大了,每次来了新请求就会锁住导致一次只能处理一个请求。后来我虽然解决了这个问题,但我觉得自己对多线程仍然是一知半解的状态。第二次,第三次作业中遇到了很多问题,我都是向别人求助才最终解决的。直到现在,我感觉自己对多线程仍然一知半解。虽然后续的oo作业应该不会再涉及到多线程,但我觉得掌握多线程的写法十分重要,将来若有机会我还是希望能够巩固这一单元所学的知识。

还有一点感触,在第一次作业中,进行关于电梯容量的判断时,我直接写了小于等于6,电梯移动一层时,我直接sleep400毫秒,在电梯移动时,我直接写了1和11。结果在第二次作业中,电梯的容量和移动一层的时间发生了变化,在第三次作业中,电梯移动的范围发生了变化。我需要把之前的数字替换成maxCapacity,moveTime,start和end,将它们声明成类的属性。在修改的时候我非常谨慎,生怕少改了哪里造成不必要的bug,好在要改的地方不多,我担心的情况并未发生。不过这也启示我在第一次写代码时就应该将常量或可变参数定义为类的属性,使得它们的含义更加清晰,并且在需要修改时更容易找到和更改。这样可以提升代码的可扩展性和灵活性,降低耦合性。

  • 38
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值