BUAA_OO_2020_Unit2 Summary
-
第一次作业
-
第一次作业除了
main
外仅设计了elevator
线程与input
线程,两者共享一个PersonQueue
对象,两个进程的协同和同步控制通过对PersonQueue
的共享访问实现。 -
其中为了避免暴力轮询,除使用基本的
sychronize
外,按照指导书的建议使用了wait、notifyall
。虽然当时对线程与普通的类之间并没有太分得清,为了安全乱用一通,但幸于四处问人搜集资料后,懵懵懂懂的进了多线程的大门,也并未出现线程安全和死锁的情况。 -
同时在
Input
结束后设置了标志位,实现不同线程间的通信。 -
-
-
第二次作业
-
第二次作业写了两版。第一版延续第一次作业的架构,安全上没有出现过多的问题。
-
第二版试着写了调度器(第一次作业完全电梯发挥主观能动性占据了调度器的位置,第二次第一版作业也让几个电梯秉持着公平公正公开原则自己去抢(才不是我懒))。加入了
object
锁以解决信号问题。但千防万防还是出现了死锁问题。在观察Jprofiler
中各线程的运行情况后,发现了是一个标志结束的notifyall
无法全部唤醒多个在waiting
状态的elevator
线程,改过之后也无大问题。
-
-
第三次作业
-
第二次作业观察比较了两版作业性能分之后交上去了无调度器的一版。但显然第三次作业让我第二次作业的第二版没有白写(叹气。
-
当然在适应加入的”换乘“功能后又出现了结束条件改变的问题,于是没有对这一情况更改的情况下出现了
ctle
,也逼着我学习了肉眼观察de
多线程bug
之外的对Jprofiler
结合线程绑定名字的更”高级“的debug方法。但不得不说printf
天下第一(。 -
本来以为
安全性没什么问题了(蜜汁自信),但在周五晚跑自己测评机的时候还是出现ConcurrentModificationException
,拿测试样例去idea跑却没有出现该异常抛出。周五晚并没有太在意,但周六上午仔细想了想确实存在着不同线程同时访问并更改一个elevator
的等待队列的情况,于是将访问等待队列的方法整理了一下放在了一起,并加了锁。但看了老师的视频我才意识到自己这样加锁好像又犯了在一个线程方法中调用另一个线程对象的方法的问题,虽然在我的实现中确实两个线程不能同时进入带锁的临界区,逻辑上没有问题,但今后还是尽量避免此类易湿鞋的情况。
-
-
总体来说三次作业都是生产者消费者模式或者其变体,没有特别大的缺陷也没有什么突出亮点。
架构设计
SOLID原则
-
SRP:单一责任原则
-
显然三次作业我对
SRP
原则都是”部分贯彻“。从整体来看,各个方法职责较为分明,没有出现某个方法负责多任务的情况。Input
负责输入,elevator
负责运乘客,PersonQueue
负责存储请求。但三次作业中,我的elevator
都是较为”智能“的,能够自己决定拉进来哪些乘客,决定开门关门及运行方向。调度器的功能被大大削弱了-
第一次及第二次作业无电梯外调度器暂且不提
-
第三次作业中调度器只负责结合电梯的状态分配乘客,也并不对电梯显式的指明前进路线,调度器仿佛一个乘客分流机。
-
-
-
LSP:开放封闭原则
-
基本上是完全无实现
-
两版无调度器与两版有调度器共有的方法基本上没有太大改动,无非是修改一下方法内部细节,但不修改而是扩展去适应新需求是完全没有做到
-
一是对未来功能的预测没有做到(哪个小机灵鬼儿能想到电梯破碎虚空而出呢
-
二是每一次作业我都尽量去拿性能分,因此每一次针对不同的需求有不同的策略,于是修改的效益大于拓展。
-
-
-
LSP:里氏替换原则
-
鉴于本次并没有除
Thread
之外的继承,于是此原则咕咕掉了
-
-
ISP:接口隔离原则
-
三次作业中该原则执行的较好,基本实现了对于不同类中方法的调用都以传参得结果的方式实现,而对于方法内部实现细节并不关心。
-
-
DIP:依赖倒置原则
-
仿佛没有抽象存在呢
-
具体分析
第一次作业
-
第一次作业是最基本的生产者消费者模式的实现,代码量并不多。
-
架构也比较简单,从
uml
图中那个可以看出只是一个托盘(PersonQueue
)一个生产者(Input
)一个消费者(Elevator
). -
从
method metrics
与class metrics
中看出确实存在前文所说的,elevator
承担了调度器的任务所以较冗杂的情况。 -
在调度方面,我将第一次的调度拆分为”方向选取“与”捎带策略“两个部分
-
当时一看指导书的捎带策略就觉得不靠谱,所以开始琢磨自己的捎带策略
-
首先关于方向的确定,我首先选择了电梯内的乘客作为研究对象,从这一角度来说,采取走到最上方再折返的策略显得有些愚蠢。于是我想,可能电梯可以在到达每一层后都再次判断下一步运动方向。即如果上方有一些到达请求,下方有一些到达请求,那么根据简单的计算可以得出,此时选取上方存在到达请求的最远距离与下方存在到达请求最远距离的最小值来定下一移动方向,可以使得总cost最小。
例如当前电梯在第5层,上方15、9、11楼层都存在到达请求,下方3楼层存在到达请求,则上方存在到达请求的最远距离为15-5=10,下方存在到达请求的最远距离为5-3=2。10>2,于是下一步移动方向为向下,此时使得总运动距离为10+2*2=14,小于10*2+2=22.
-
此时再把视线转到电梯外的等候队列。考虑电梯的移动方向是否与电梯外的乘客有关。若无关,则可能存在无法接到电梯外乘客的情况,所以电梯移动方向同样需要受电梯外请求的影响。鉴于没有想到特别好的方法且电梯并无容量限制,且只有一部电梯所以电梯外请求躲也躲不掉迟早要负责,于是将电梯外的请求与电梯内的请求放在了同一等级,都同水平的影响电梯的移动方向。
-
对于捎带策略,鉴于自己的方向选取并不是正常的
look
,如果不接同层反方向的乘客则会出现——电梯在乘客面前反复横跳但双方都无能为力的局面。所以总策略是到达一层后,接上该层所有乘客,后判断下一步运动方向。
-
-
在电梯的运动方面,将电梯的各项动作拆分,包括
move、needopen、needclose
等,使得电梯的各项活动互不影响。
第二次作业
-
首先关于两版有无调度器的选择,我仅针对中测的数据做了多次实验。
第一版 | 第二版 | 随机分配 | 抢电梯 | 第三版 | 改了抢电梯 | 持续抢电梯 | ||
---|---|---|---|---|---|---|---|---|
22.602 | 20.5774 | -2 | -2 | 22.6434 | 22.9777 | 20.xxxx | 22.6353 | 22.1889 |
17.7045 | 10.7637 | -7 | 14.5441 | 19.3058 | 17.7132 | 19.2878 | 11.408 | |
13.6644 | 15.7331 | 2 | 14.5426 | 13.4399 | 14.471 | 14.5162 | 13.8156 | |
20.9938 | 21.3915 | 1 | 19.7854 | 21.8242 | 21.8121 | 22.9988 | 21.8966 | |
21.276 | 22.4918 | 1 | 20.5436 | 17.5411 | 23.2708 | 19.1895 | 18.4677 | |
36.1957 | 35.6979 | -1 | 3 | 32.4027 | 31.9091 | 39.0204 | 34.232 | 33.3626 |
51.5544 | 55.5447 | 4 | 53.5772 | 49.5375 | 53.9672 | 52.7389 | 49.5701 | |
46.8449 | 43.1451 | -3 | 44.701 | 45.9367 | 51.197 | 50.277 | 42.7724 | |
41.981 | 46.777 | 5 | 44.2593 | 46.238 | 47.8224 | 46.2736 | 41.0372 | |
39.1994 | 37.7807 | -2 | 37.2335 | 34.7221 | 39.4612 | 42.7555 | 35.1545 |
-
从上表也可看出,就我自己的实现而言,抢电梯的收益远高于加了粗糙的调度器。于是在周五晚放弃了调度器的策略,专心精进抢电梯的改进。
-
由于扬了调度器,所以该次作业与第一次作业的总代码量、方法等并没有明显区别。
-
但因加入了多个电梯,当时也并无线程池的概念,就偷鸡的把所有电梯线程通过
ifelse
实现,导致Main
飘红。 -
关于抢电梯的策略,在本次所有电梯都处于同等地位的前提下,其合理性还是值得肯定的。但我想就本次抢电梯的细节进行讨论。
-
在本次作业中可以很明显的看出,简单的改一点细节就可以对最终的时间产生挺大的影响。首先是抢电梯策略本身所造成的聚集性,可能使得部分电梯长时间处于在追赶的状态但没有理会较远请求。于是思考电梯分层,但这又与自己的抢的理念不相符,且在所有电梯地位等同的状态下并不会有太好的效果。所以最终和ljh小姐姐讨论后决定采取扬电梯的策略。即在刚开始将部分电梯扔到最高层/最底层,使得初始条件电梯的分散。(这一策略至少对于某一中测点有奇效)
-
其次是对于电梯方向的选择与对捎带策略的改变,鉴于此次多部电梯使得一部电梯接不到某一乘客,其也会有其他电梯负责,所以改变了每层都改变方向的策略回归了look算法的怀抱,并对捎带策略改为只接同方向运行的请求。(其实是经过了多次更改实验才不情愿的承认自己不对,哭着跑到
look
怀里)。当然如果此时电梯内人满了,而且上下都有到达请求的情况下我还是偷摸着背着look去联系自己第一次作业的方向选择了(。 -
仅仅改了细节都能将性能分提一大截,想想一开始在纠结调度还是不调度的自己仿佛一个憨憨。
-
第三次作业
-
前两次偷鸡自己抢不写调度器的后果就是第三次加调度器加到怀疑人生。还好我第二次写了个基本框架(doge)。虽然我的dispatcher似乎就是一个请求分配器。同时对于用户的等待时间...
呵扬了吧,考虑到用户的等待时间与总运行时间应该有某种关联(瞎猜的,也就没有再刻意在这一点下功夫。 -
调度器写的大多数方法我后来反思了下基本上是没用的,因为当时并没有去仔细研究本次所给各种电梯可达楼层的关系,仅凭个人感觉写了很多根本不会发生的情况。但也懒得删了(。
-
关于elevator此次除了前两次都有的内部队列存储电梯内部人员之外,增加了每部电梯的等待队列,每部电梯仅可见这两个队列,并根据这两个队列进行运行的控制。
-
鉴于此次会实现请求的拆分,所以把
personrequest
包装了一个person
类,加了dir
属性表示请求的方向,加了flag
属性标明该请求是否是被拆分的第二步请求,防止出现换乘之后的运行早于换乘前的运行。 -
(我知道你们不会看
uml
图的)总体逻辑是从input
读入一个请求,将该请求加入到personqueue
中,dispather
线程发现personqueue
不为空即对personqueue
头的请求进行分配。即dispatcher
通过personqueue
与input
进行交互,并单方面对elevator
分配请求。 -
加入的调度器的逻辑如下:
-
对于输入的请求首先判断是否存在某种电梯具有运送该请求的能力,若不存在,则扔到split中进行拆分。若存在,则判断在具有运送能力的几种电梯中是存在可以捎带该请求的电梯,若不存在,则按照选择几种电梯中人员最少的某部电梯,并把请求塞到该部电梯中。若存在,则选择可以稍带的几部电梯中距当前电梯最近的某部电梯,把请求塞到该部电梯中。
-
事实上随机的情况下并不会有多部电梯同时满足上述条件的情况发生,但是写都写了(。
-
关于
split
需要反思,明明是最应该重视的一块,我却只是按照静态的换乘写了。并没有将电梯的运行情况考虑进去。 - 对每一个person的flag属性设定如下:
- 若为直达,则设置为0
- 若为换乘第一阶段,则设置为1
- 若为换乘第二阶段,则设置为-1
- 在扫描当前等待队列时,不理会flag为-1的person
- 在处理person出门时,检查person的flag是否为1。若为1,则扫描其他电梯中的person,找到同id的person,把其flag从-1改为0.
- 这种情况下避免了电梯“死掉又不得不唤醒的情况”。
-
为了方便记录加了
serial
类,显得有些突兀。elevator
与dispatcher
大面积飘红需要反思。 -
针对不同电梯的加锁使用了多个不同的锁,冗余繁杂,但我还没想好办法解决orz。
-
可扩展性方面, 仿佛不太有。
BUG分析
-
本单元作业强测、互测均未发现
bug
-
公测中出现了
ctle
也已经在上文分析过,也出现过WA
后检查是扬电梯的时候忘了筛掉0。 -
同时在第二次作业前期也出现了没好好看指导书不知道电梯数是可变的....(还好当时还没交
-
在互测的时候,第一次互测发现每扔一个点都能刀到一个人还挺开心,就隔一段扔一个,最后才意识到是一直在刀同以个人的
ctle
,感到无比愧疚orz。第三次互测发现某个孩子对于电梯的人数限制控制的不好导致超载的情况。 -
除了基本的边界情况,如同一时间投入多个请求测试自己程序的抗压性、投入所有可能的情况测试程序覆盖的全面性(23*23),都是扔给自己的测评机跑了。于是第二次测评机死掉也是咕掉第二次互测的原因(。
心得体会
-
三周入门多线程还是挺开心的,虽然过程不太愉快吧,但多线程的痛苦与惊喜并存,除了纠结性能的时候比较头疼,其他的时候发现自己的程序出现从来没有出现过的现象还是惊喜与惊吓齐飞的。当然可视化没有实现、评测机不够智能,算法的比较局限于小部分数据,拓展性又咕了.......
-
最深的体会就是重视于细节,三次作业95、99、99的变化也是我的捎带一步步弄清的过程。仅仅改一个条件就能修改
ctle
、提高几秒的性能...... -
其次的体会是别太自信了,本来对于指导书的换乘我是不屑一顾的,但后来我就不调头真香了
-
然后就是合作的重要性,不管是思路的讨论,还是测评机的开发,两个人的力量总是大于一个人的。这三次作业刚开始我一直都在怀疑自己的思路,迟迟不愿下笔,但通过讨论思路总是会更加明确,也更能从不同的角度认识问题。实名夸赞ljh小姐姐,本来我是不想写测评机的觉得写不出来(才不是懒),但ljh小姐姐每次都坚定的写测评机的行为深切的鼓励了我,于是摸出来了测评机(发现也没花费太多时间反而解放了自己),学会了各种奇奇怪怪的操作,也是一种自我提升。
我仿佛在学python。俺永远爱ljh(大声。自信的小姐姐真是太可爱了 -
最后感谢讨论区与各位帮忙解答问题的大佬们,真诚笔芯。
-