OO第二单元总结
OO第二单元总结
OO第五次作业
本次作业思路
- 在反复研究了上机的代码实验思路后,在多线程的调用沿用了上机代码的框架。
- 在自己学习的时候突然知道了
CopyOnWriteArrayList
是线程安全的容器。在讨论群发现大家都是这个地方出问题,所以果断运用了这个容器(σ゚∀゚)σ..😗(其实一开始是计划等过了再使用线程不安全的容器写一遍的,但是写完之后改完bug太高兴了就忘了|ू・ω・` )) - 在设计
Request
类时,除了正常的属性fromFloor,toFloor,fromBuilding,toBuilding,personId
,外我还加入了private int direction
,private int evaluid
。在最初的设想中direction之所以设置为int
是因为可以将向上的电梯设置为1,向下的设置为-1,可以用于判断运动方向,更重要的是电梯的移动可以变为place = place + direction
,而且可以用*direction
判断处理是否需要更改目的地。(比如无论是上升还是下降。如果当前destination*direction<新的请求的目的地*destination
便更改目的地。无需提取direction
之后进行特殊判断。比如下降时从5到3楼,此时出现了从4楼到2楼,由于-3<-2,更改目的地)但是由于后面的实现过程中出现了一点bug,在第一个请求就是向下运动时单纯地靠这一个标志没有想到很好地解决方法,最后还加了一个标志int flag2
用来实现。不过这个点因为减的时候没有private int evaluid
的设置单纯因为后面每次操作时换算太麻烦了。+1
还出现了bug o((⊙﹏⊙))o。 - 在各种的操作的实现中运用了一个单独的类
Operate
,其中需要传入当前的请求Request,当前电梯位置place从而进行opne,close,in,out,move
等动作。 - 在
Process
类中实现电梯的调度策略,使用变量wait
来存储目前电梯的请求,使用变量evaluater
来存储目前在电梯的请求,destination
来表示电梯的目的地,变量int flag2=1
,以及变量request = null
记录当前请求。- 在每次循环开始时,如果电梯中存在请求那么
request = evaluater.get(0);
否则request = wait.addRequest();
(在wait
中存在两种获取请求的方式——get
add
,两者区别是add
不会删除元素。) - 如果电梯中存在请求那么
destination = request.getFromFloor();
,否则destination = request.getToFloor();
。这样设置是为了避免在开始时电梯收到的请求是下降,比如在开始时电梯在1楼收到的请求是从6楼去5楼,这是电梯应该先到达6楼,即电梯的目的地是6楼。 - 如果
place > destination
表示此时电梯应该向下运动flag2 = -1;
,否则是向上运动flag2 = 1;
. - 在运行的途中,在每一层遍历
wait
查看是否有同向的请求,如果方向相同且没有超载就让其登上电梯。且每一层设置flag
用来解决在一层上两个人时导致开门两次的问题,同时还可以用来判断是否需要关门操作。当有乘客上电梯时进行判断if (k.getToFloor() * k.getDirection() > destination * k.getDirection())
,如果为true,那么destination = k.getToFloor();
。
- 在每次循环开始时,如果电梯中存在请求那么
类图
本次作业的bug
- 在第一次自测的时候发现自己写了一个克隆机,一个人进去三个人出来(果然代码不能隔夜,第二天忘记自己之前已经写了乘客进入电梯的操作)。
- 在弱测的时候出现了同一个代码提交,但是不同测试点出问题的情况,后来询问同学之后才发现是出现了线程问题,自己在额外写
addQuest()
方法的时候忘记了唤醒的操作。 - 在互测的时候因为同学说他电梯写超载了,所以自己好奇测试了一下,结果发现自己也写超载了,判定语句用的
size>6
ヽ(ー_ー)ノ - 在调度策略上出现了一些问题,在写的时候将主请求的方向作为了乘客能否上电梯的条件而不是电梯运行的方向,使得在强测的时候出现了超时的行为。
OO第六次作业
作业思路
-
本次作业主要增加的是横向电梯和可能同一个楼层或楼座多部电梯横向电梯或者多部纵向电梯。
-
横向电梯:由于横向电梯是环形所以判断其运动方向较与纵向电梯复杂,采用了一种比较直白且暴力的方式来判断其运行方向(目前没想到更好的办法)
if (destination > place) { if (destination - place < 3) { flag2 = 1; } else { flag2 = -1; } } else { if (place - destination < 3) { flag2 = -1; } else { flag2 = 1; } }
对于电梯的调度策略,我采用的是使用接等待队列中的第一名乘客的方向作为电梯的运动方向,让电梯一直沿着该方向进行运动,直到电梯内没有乘客后,继续以接等待队列中的第一名乘客的方向作为电梯的方向从而完成运送任务。
-
多部电梯:使用的是自由竞争的策略,对于相同楼座或者楼层的不同线程使其共享一个等待队列,让他们自由选择,不过在某次研讨课上,”强老师“提出过不能让两个线程读到同一个请求且都将其作为主请求。我的做法是确认主请求后将其取出来,如果没有将该乘客接到电梯上则将其放回等待队列,且在其他过程中都保证队列的读取操作线程安全。
-
类图
本次作业出现的bug
- 这次出现了一个很严重且难找的bug,导致找了四天才找出来。由于第一次作业写的时候没太明白输出线程不安全的含义,导致在第一次作业没有对于输出进行封装,导致了这个bug。
- 在进行调度的时候由于标志位设置过早,导致横向电梯在第二次经过初始位置时不会输出移动到该电梯,例如在一次循环中经过了
B-A-B
,那么第二次经过B座时,将不会输出。
OO第七次作业
本次作业思路
-
本次作业主要是增加了横向电梯定制停靠楼座的电梯增加请求,以及需要处理由此产生的可能某位乘客需要进行电梯换成的请求。由于题目的设置,使得当请求发送时必然至少存在一个有效路径,使得乘客能够到达其目的地,所以在本次的作业中增加了类
SplitRequest
、order
,SplitRequest
这个类的作用主要为在请求申请时将请求进行分割为若干个order
,随后存入请求中,再分派进入调度的盘子分发至各个电梯随后根据存有若干个order
对象的容器中的第一个请求来决定其乘坐的电梯和运行的方向(但是第一次没有将情况考虑好,在这个地方出现了一个致命的bug) -
本次作业还有一个难点便是各个线程的结束,在之前的两次作业中由于没有换乘操作,使得线程的结束很容易就进行判断结束了,但是在本次作业中不能使用和之前作业相同的判断方式,因为可能导致换成的乘客还没有到达想要换成的电梯时该电梯的线程已经结束了。在本次的作业中我是用了一个新建的类
StopFlag
,这个类中有一个共享的成员变量private static int num
,每当一个请求输入时该变量加一,请求结束后变量减一,当前两次的暂停条件满足且num==0
时,这时表示所有的请求处理结束,唤醒所有线程并且结束。(在这一步实现的时候遇到了很大的问题,这个问题在后文讲述,通过这个点我也明白自己对于多线程的一些内容的理解有多糟糕不够充分)
本次作业出现的bug
- 本次作业中出现了ctle的情况,其中出现ctle的主要原因是在拆分请求的时候,比如 B1-D7第一步应该是B-D,但是在解析的时候忽略了这种情况,弄成了B1-B1,导致程序无法处理该请求从而一直循环造成了轮询。
- 其次是上文提到对于结束条件的使用。虽然在上文中已经提到了解决的办法,但是在实施的时候才发现了自己对于多线程理解的一个重大的错误。在最开始的时候我仅仅在电梯案中使用了
notifyall()
使得其他的线程一直无法结束。经过一个多小时的不断地调试才明白,notifyall()
仅仅唤醒了未能获得某个对象的锁的线程,并非所有沉睡的线程。所以最终建立了一个函数来将进行一个个地唤醒。
时序图
同步块的设置和锁的选择
- 在本次的作业中对于锁的选择是运用的关键字
synchronized
,当多个线程对于同一个对象进行读写操作时,可能会发生线程不安全的情况。在本次的作业中主要是对于请求队列进行了读写操作,以及需要对输出进行封装,为了防止在对请求队列中增加和获取请求信息时产生问题,需要将对于请求队列中元素的修改的操作的方法锁住。随后在增加多部电梯之后,由于害怕产生问题,我在寻找合适的请求进入电梯遍历请求时对于请求列表也进行了锁。
架构设计的体验
- 本次作业的架构比上一个单元好了很多(不过很大程度是因为借鉴了实验的代码)由于实验的架构十分清晰明了,在三次作业中都沿用了该架构设计,不过可能是由于捎带策略的问题,使得仍然存在某个方法写的过长,耦合度较高的问题,这个问题对我来说十分困扰,每次都尽力去按照面向对象的要求来完成作业,但是结果都不尽人意,这个问题也会在日后的作业中更多地进行思考思考。在后两次作业中由于时间比较紧张,为了求稳导致自己在增加横向电梯时一味地新增加实验的架构,造成了后两次作业架构的复杂冗余(从后两次复杂的类图就可以看出)。但是经过第一次作业良好的架构设计,使得在后两次作业的迭代开发中,没有出现大规模地修改,让我体会到了好的架构的好处。
调度器的设计
在本次的作业中主要是由类'InputThread'来对于输入进行处理,随后分入类Schedule
,随后类Schedule
对于请求按照楼层、楼座进行分配到对应的请求队列中。在电梯的线程运行时,当其需要获得请求时,会对于请求队列进行相应的操作。
心得体会
第一次写电梯,在之前通过学长了解,感觉可能是一个很可怕的事情,但是可能是因为第一次作业比较简单亦或是运用了实验代码的框架,感觉没有想象中那么困难,通过本单元的作业我也初次接触了多线程这个神奇的东西,有点打破了我对于程序的理解,通过作业的练习我也更加深刻地了解到了多线程一些初级的运用(在研讨课上老师和助教说以后会学习更加高级的运用方法,希望自己有机会能够继续向更深层次方面学习)。在第一次作业中由于自己捎带策略的问题,让自己在bug修复阶段十分头疼,也可能是因为隔得时间比较长,忘记了自己的策略,改了好几次才想到正确的办法。在三次作业中总是会出现一些奇奇怪怪的的bug,比如第一次作业中的超载,第二次作业中标志位的改变和判断标志位顺序写反了,第三次作业中对于请求拆分的错误,由于多线程的原因,导致出现WrongAnswer
时寻找bug十分的头疼,在一开始时自己是十分抗拒的态度,导致第二次作业弱测没能全部通过,后来才沉下心来讲每一栋楼,每一层楼的请求单独提出来进行debug才完成了bug修复。
通过这几次作业特别是第三次作业notifyall()
的使用让充分让我理解到了自己对于多线程的理解有多么地不足,在第三次作业之前自己自认为花了一些功课对于多线程有了一些了解,但是这些低级的错误让我彻底认清现实,让我明白了自己还需要花更多地时间去学习。通过本单元的作业,我也有了更大的收获,在来计院之后我是很少和同学一起去讨论完成作业的,因为由于思路的不同会导致实现的方式不同,而且由于作业都是大量的代码很不好意思去去麻烦他人debug,所以在作业方面和同学交流地越来越少。而在本单元的作业中有很多同学提供了他们的测评机给我,也有同学替我看完了代码来帮助我完善,非常感谢他们的帮助,希望以后也有能够帮得到他们的地方(ΦωΦ)
虽然电梯的作业很难,在改bug的过程中很糟心,但是在完成这单元的作业之后,确实收获很多,也很有成就感。