“写之前没想到会这么玄学。”
三次作业的设计策略
电梯整体上采用了look算法,原理比较简单,但是实现起来会有很多细节。
结合之前实验的消费者模式,将Building作为一个tray,然后将涉及改变Building中人数的操作加了synchronized。
第一次作业
只有一部电梯而且不限制人数,把building划分为floor,设置了input和elevator两个线程,只有放入和取出这种涉及改变Building内人数的操作上锁。
第二次作业
刚开始指定电梯数目,与第一次相比只是增加线程,另外在电梯上人的时候加一个人数限制即可。
第三次作业
涉及实时增加电梯,我增加了一个Elevators类,用来在读取到增加电梯的指令时增加电梯并启动。
关于换乘,我发现三种电梯都可以到达1和15层,也就是说,只需要判断电梯内这个人是换乘还是可以坐现在这部电梯直达,然后在1和15层把换乘的人全部丢出去就可以了。
于是在每个楼层设置了通过电梯A/B/C向上/向下直达的六个arrayList,还有一个需要换乘的arrayList。方便电梯到达时上人。
相应设置了一个path,存储了从每个楼层出发达到别的所有楼层可以坐一种电梯直达还是需要换乘。这里对一些可以多种方式达到的需求只选择了一种,这样操作影响了性能,但是后续没有优化。
最后进行了一些小优化(也不能说是完全的优化,因为还是会有些情况是降低了性能)。每到达一个楼层,都对换乘队列进行遍历,如果可以从当前楼层直达,那就出电梯等待另一部电梯直达。这样有的人就可以提前下电梯,留出空间。
第三次作业架构设计的可扩展性
涉及换乘,path是相当于自己手写的,如果更改电梯可到达楼层,这就需要重新分析,不适合扩展。
而电梯运行方面,电梯运行的大致流程已经确定,后续可以对某些函数进行操作来适应需求的更改。
关于性能
觉得对于某种策略来说,总会有一些情况刚好让电梯运行的时间比较长,就像第二次作业一样,在强测中别的点都是98-100,有一个点是84。所以考虑优化的时候,虽然有一些办法, 最后还是没有进行。
而第三次作业的性能分就比较平均(就很神奇)。
如果要优化,在电梯上人的时候或许可以进行选择,例如电梯a在14层,现在里面的人要去15层,还可以上一个人,而这里有很多人要去20层,这时如果拒载,就只需要去一次15层,一次20层;如果直接上人,就需要去两次20层。但是这种情况也是非常理想的,如果在到达15层之后进入一个乘客要去-3层,这样14层要去20层的人就眼睁睁看着电梯又下到了-3,原地爆炸。
觉得自己的算法很难做出能绝对提升性能的优化,如果要优化,可能还是需要考虑其他的调度算法。
基于度量的分析
由于刚开始理解多线程是依据了实验的生产者消费者模式,自己的设计也比较清晰,生产者就是Input,消费者就是Elevator,Tray就是Building,而Building又拆分成了Floor。只在第三次作业中,为了加电梯增加了Elevators。
电梯的run复杂度一直都很高,非常高。分成几个方法之后,这些方法的复杂度都很高,非常高。
第三次作业为了换乘加了一些操作后,它们也红了。
第一次作业
method | ev(G) | iv(G) | v(G) |
---|---|---|---|
Elevator.noChange() | 1.0 | 6.0 | 12.0 |
Elevator.open() | 6.0 | 11.0 | 13.0 |
Elevator.run() | 6.0 | 19.0 | 24.0 |
第一个是方向的判断,这里有一些多余的判断,思路有些乱。后面在第三次作业中把判断方向是否改变及改变方向合成了直接选择方向,这样思路就清晰了很多(然后高复杂度的方法又多了一个)。
第二次作业
method | ev(G) | iv(G) | v(G) |
---|---|---|---|
Elevator.noChange() | 1.0 | 6.0 | 12.0 |
Elevator.open() | 6.0 | 11.0 | 13.0 |
Elevator.run() | 6.0 | 19.0 | 24.0 |
没错跟第一次作业的表格是一样的。因为第二次只涉及增加电梯,运行的策略并没有什么变化。
第三次作业
爆炸
method | ev(G) | iv(G) | v(G) |
---|---|---|---|
Building.downHasRequest(String,int) | 1.0 | 7.0 | 12.0 |
Building.upHasRequest(String,int) | 1.0 | 9.0 | 14.0 |
Elevator.directionChoose() | 1.0 | 6.0 | 12.0 |
Elevator.Elevator(Building,String,String,HashMap<>) | 1.0 | 9.0 | 17.0 |
Elevator.judgeIfOpenAndCope() | 1.0 | 21.0 | 27.0 |
Elevator.open() | 2.0 | 11.0 | 13.0 |
Elevator.run() | 4.0 | 8.0 | 12.0 |
MainClass.createPath() | 1.0 | 14.0 | 28.0 |
我能怎么办它们就是好复杂(X
第四行的方法应该是
Elevator(Building building,String eleId,String eleType, HashMap> path)
因为楼层要去掉0层,遍历经常需要拆开讨论,很多地方由于这个原因复杂,例如前两个method,本可以一个循环解决的问题。或许可以考虑一下给楼层们编个号,这样需要根据楼层编号获得楼层的时候也可以直接使用ArrayList而不是搞一个HashMap。
自己的bug
第一次作业没想到电梯运行的很多细节,bug集中出现在电梯方向的改变方面。经常出现在两层楼之间来回往返而不接人的情况。
第二次作业电梯运行的策略没有变,其他的改动也没有出bug。
第三次作业由于涉及换乘,并把电梯内的乘客分成两种(换乘与直达),就把电梯开关门、方向设置等操作写成单独的方法。通过再次回顾,相比前两次作业更清晰了思路,然后将方向的判断放在了电梯移动一层之后。
很多bug都是上面的原因。
此外还遇到了运行无法结束的问题,在Building中添加了判断,如果输入结束而且所有乘客都到达了目标楼层,就表示已经结束,除了最后结束的电梯,其他电梯都是wait状态,最后完成的电梯会将其他电梯唤醒,进行判断并结束运行。
发现别人程序bug所采用的策略
用java写了玄学评测机。很多时候出的错误无法复现。
数据生成
随机生成三种指令,上人、延时与加电梯。
验证
关于电梯,考虑了超载问题,检验了电梯移动一层的时间,电梯可以开关门的楼层,用状态判断的方法判断了开关门及移动是否合理。
关于乘客,同样根据输出数据对乘客的移动进行了模拟,不正常的情况都会被直接判断为错。
缺点
如果电梯运行出现死循环,那么就无法检验结果是否正确,也不会存引发错误的数据。不过目前只在自己debug的时候出现过这种情况。
心得体会
终于不是每周一次重构。
第一次作业理解多线程花费了一些时间。第二次作业只进行了简单的修改。我们的第一次作业就是去年的第二次作业,然后发现第三次作业倒是没有想象中那么麻烦。很多东西还是套用了之前的。
自己写多线程只用了synchronized,事实上可用的方法还有很多,自己的了解还不够多。如果了解更多,或许会有别的方法进行优化。
最后,电梯好玄学。