BUAAOO 第二单元总结
第一次作业
同步块的设置和锁的选择
第一次作业比较简单,我就设计了两个线程(不包括main),一个是电梯线程,一个是输入线程,两者之间共享一个等待队列,所以锁基本上都是加在等待队列上的。如输入线程输入请求的时候,往共享的等待队列里面加人的方法,电梯从共享的等待队列里面找人、取人的方法等涉及共享对象的方法加上了锁。
调度器设计
第一次作业由于只有一部电梯,所以我没有设计调度器。
如果说电梯内部的调度也属于这一范畴的话:我第一次作业中电梯的捎带是采用ALS算法,基本上和指导书里面写的一模一样。
分析图及可扩展性
类关系图
复杂度统计
method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
Elevator.closeDoor() | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.Elevator(String,HashMap>) | 1.0 | 1.0 | 2.0 | 2.0 |
Elevator.getIn() | 23.0 | 7.0 | 9.0 | 10.0 |
Elevator.getOut() | 4.0 | 1.0 | 4.0 | 4.0 |
Elevator.getSize(HashMap>) | 1.0 | 1.0 | 2.0 | 2.0 |
Elevator.getTop() | 6.0 | 3.0 | 4.0 | 5.0 |
Elevator.go() | 66.0 | 11.0 | 19.0 | 24.0 |
Elevator.goNight() | 8.0 | 3.0 | 5.0 | 5.0 |
Elevator.isEmpty(HashMap>) | 3.0 | 3.0 | 2.0 | 3.0 |
Elevator.openDoor() | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.run() | 6.0 | 1.0 | 4.0 | 4.0 |
Elevator.runMorning() | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.runNight() | 1.0 | 1.0 | 2.0 | 2.0 |
Elevator.runRandom() | 1.0 | 1.0 | 2.0 | 2.0 |
Elevator.setNoInput() | 0.0 | 1.0 | 1.0 | 1.0 |
InputThread.InputThread(HashMap>,Elevator,ElevatorInput) | 0.0 | 1.0 | 1.0 | 1.0 |
InputThread.run() | 5.0 | 3.0 | 4.0 | 4.0 |
MainClass.main(String[]) | 1.0 | 1.0 | 2.0 | 2.0 |
Request.getArriveFloor() | 0.0 | 1.0 | 1.0 | 1.0 |
Request.getDirection() | 0.0 | 1.0 | 1.0 | 1.0 |
Request.getId() | 0.0 | 1.0 | 1.0 | 1.0 |
Request.getLeaveFloor() | 0.0 | 1.0 | 1.0 | 1.0 |
Request.inToString() | 0.0 | 1.0 | 1.0 | 1.0 |
Request.outToString() | 0.0 | 1.0 | 1.0 | 1.0 |
Request.Request(String,int,int) | 0.0 | 1.0 | 1.0 | 1.0 |
Total | 126.0 | 49.0 | 73.0 | 81.0 |
Average | 5.04 | 1.96 | 2.92 | 3.24 |
class | OCavg | OCmax | WMC |
---|---|---|---|
Elevator | 3.933333333333333 | 21.0 | 59.0 |
InputThread | 2.0 | 3.0 | 4.0 |
MainClass | 2.0 | 2.0 | 2.0 |
Request | 1.0 | 1.0 | 7.0 |
Total | 72.0 | ||
Average | 2.88 | 6.75 | 18.0 |
可以看到,我的第一次作业中Elevator复杂度较高,尤其是getIn和go这两个方法,前者是因为包含了判断各种情况的过程,实际上应该将其分解成更加原子化的操作,后者也是,go的方法本意是到达下一层,实则我将将方向的选择也放进了这个方法,导致复杂度升高了许多。
顺序图
这次作业的可扩展性极差,直接导致了我的下一次重构,原因有如下几点:首先是一些电梯的属性没有设计好,对于一些参数(运行速度之类),我都是直接写在了程序里面,没有配备一个属性,这是不太好的,其次是没有将共享对象非常清晰的拎出来,再者是电梯的运行方法过于杂糅,没有实现一套原子化的操作,对于后续的扩展十分不利,最后是算法,我在第一次作业中采用了ALS算法,显然是失败的。
BUG分析
自己的作业:在最后的公测以及互测中都没有发现BUG。但是在做的过程中还是有BUG的,主要在于电梯的逻辑处理不到位,所幸这么烂的架构并没有出现线程安全问题。我认为还有一点可以认为是BUG的就是ALS算法(实在是太慢了),以及我没有专门写MORNING的电梯运行策略,结果导致强测分数不是很高。
别人的BUG:这次作业的互测确实比较困难(至少对于我而言),因此我也更加没有兴致(能力)去写评测机了,主要的评测手段就是以自己曾经出现的BUG为原型构造数据,以及构造一些针对算法的极端数据,这一过程非常艰辛,结果却是无功而返。
心得体会
说实话,虽然老师说第一次作业非常简单,甚至是OO作业中难度最低的一两个作业,但是我在第一次作业上面花费的时间远远多于后面两次作业,甚至一度感觉要做不完。原因主要有如下几点:首先,第一次作业的架构是一个比较大的问题,从0到1远远难于从1到2,或者是从1到10。我刚开始的时候感到非常的迷茫,不知道如何写才是一个“好架构”,也不知道从何下手。其次,对线程的理解不到位,只是对一个新的事物感到非常害怕,生怕动手就写错,然而实际上并不困难。最后,没有一个层次化的设计思维,刚开始设计的时候总是会因为害怕出错而辐射联想出与这个方法相关好多东西,就比如写一个开门操作,就会想“什么时候可以开门,如果不开门,下一步是做什么,需要判断哪些东西”,其实就是没有缕清思路,将各种想法都杂糅在了一起。我觉得解决这种问题的方法之一就是在纸上画出层次结构图,先将层次划分好,细节变做变加,只要不影响整体的架构即可。
第二次作业
同步块的设置和锁的选择
第二次作业我进行了重构,将共享对象全部拎出来放到各自的类里面,并且将涉及共享对象的操作(主要是涉及线程安全问题的方法)都在这个类里面实现,然后在哪里加锁就十分清晰了。
主要是三个线程:输入input,调度器scheduler,电梯elevator。
他们之间的共享对象是:input与scheduler之间共享一个rawRequests,input负责将输入的请求放进去,scheduler负责将里面的请求拿出来并且分配到电梯里面去。
这些方法是input和scheduler中涉及共享对象的操作,我将其全部放进了RawRequests这个类中,即创建共享对象rawRequests的类。
这些方法是scheduler和elevator中涉及共享对象的操作,我将其全部放进了WaitRequestMap这个类中,即创建共享对象waitRequestMap的类。
不允许线程自己使用非规定的方法,好处在于不容易出现线程安全问题,就算出了问题也容易定位了。
加锁的操作也都在这两个类中实现了,实现了比较安全的线程操作,是我认为本次作业中最为成功的一点。
调度器设计
第二次作业我设计了调度器,说是调度器,其实我并没有实现什么高性能的调度算法,实际上就是平均分配,由于第二次作业并没有涉及不同的电梯,所以这样的调度算法也说不上是特别的低效,甚至在某些时候性能更好(实则是偷懒)。
如果说电梯内部的调度也属于这一范畴的话:我第二次作业中电梯的捎带是采用LOOK算法,总体思路就是往上或者往下跑最远,期间能捎带的就捎带(同向且人未满)。
分析图及可扩展性
类关系图
复杂度统计
method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
Elevator.getIn() | 33.0 | 6.0 | 14.0 | 16.0 |
Elevator.go() | 20.0 | 8.0 | 4.0 | 9.0 |
Scheduler.run() | 17.0 | 4.0 | 7.0 | 7.0 |
Elevator.runRandom() | 13.0 | 5.0 | 6.0 | 6.0 |
Elevator.setDesFloor() | 10.0 | 1.0 | 6.0 | 6.0 |
Input.run() | 9.0 | 3.0 | 6.0 | 6.0 |
WaitRequestMap.getMaxFloor(int) | 5.0 | 1.0 | 3.0 | 5.0 |
WaitRequestMap.getMinFloor(int) | 5.0 | 1.0 | 3.0 | 5.0 |
Elevator.getOut() | 3.0 | 1.0 | 3.0 | 3.0 |
WaitRequestMap.elevatorIsEmpty() | 3.0 | 3.0 | 2.0 | 3.0 |
Elevator.Elevator(int,int,String,WaitRequestMap) | 1.0 | 1.0 | 2.0 | 2.0 |
Elevator.getSize(HashMap>) | 1.0 | 1.0 | 2.0 | 2.0 |
Elevator.run() | 1.0 | 1.0 | 2.0 | 2.0 |
RawRequests.getRequest() | 2.0 | 2.0 | 2.0 | 2.0 |
WaitRequestMap.WaitRequestMap(int) | 1.0 | 1.0 | 2.0 | 2.0 |
Elevator.arrive() | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.closeDoor() | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.openDoor() | 0.0 | 1.0 | 1.0 | 1.0 |
Input.Input(RawRequests,ElevatorInput,ArrayList,Scheduler) | 0.0 | 1.0 | 1.0 | 1.0 |
MainClass.main(String[]) | 0.0 | 1.0 | 1.0 | 1.0 |
Person.Person(int,int,int) | 0.0 | 1.0 | 1.0 | 1.0 |
Person.getArriveFloor() | 0.0 | 1.0 | 1.0 | 1.0 |
Person.getDirection() | 0.0 | 1.0 | 1.0 | 1.0 |
Person.getId() | 0.0 | 1.0 | 1.0 | 1.0 |
Person.getLeaveFloor() | 0.0 | 1.0 | 1.0 | 1.0 |
Person.inToString() | 0.0 | 1.0 | 1.0 | 1.0 |
Person.outToString() | 0.0 | 1.0 | 1.0 | 1.0 |
RawRequests.RawRequests() | 0.0 | 1.0 | 1.0 | 1.0 |
RawRequests.addRequest(Person) | 0.0 | 1.0 | 1.0 | 1.0 |
RawRequests.isEmpty() | 0.0 | 1.0 | 1.0 | 1.0 |
RawRequests.isOver() | 0.0 | 1.0 | 1.0 | 1.0 |
RawRequests.setOver() | 0.0 | 1.0 | 1.0 | 1.0 |
Scheduler.Scheduler(ArrayList,RawRequests) | 0.0 | 1.0 | 1.0 | 1.0 |
Scheduler.addWaitRequestMap(WaitRequestMap) | 0.0 | 1.0 | 1.0 | 1.0 |
WaitRequestMap.floorIsEmpty(int) | 0.0 | 1.0 | 1.0 | 1.0 |
WaitRequestMap.getRequestList(int) | 0.0 | 1.0 | 1.0 | 1.0 |
WaitRequestMap.getWaitMapId() | 0.0 | 1.0 | 1.0 | 1.0 |
WaitRequestMap.isOver() | 0.0 | 1.0 | 1.0 | 1.0 |
WaitRequestMap.putRequest(Person) | 0.0 | 1.0 | 1.0 | 1.0 |
WaitRequestMap.removeRequest(int,Person) | 0.0 | 1.0 | 1.0 | 1.0 |
WaitRequestMap.setOver() | 0.0 | 1.0 | 1.0 | 1.0 |
Total | 124.0 | 65.0 | 90.0 | 102.0 |
Average | 3.024390243902439 | 1.5853658536585367 | 2.1951219512195124 | 2.4878048780487805 |
class | OCavg | OCmax | WMC |
---|---|---|---|
Elevator | 4.090909090909091 | 13.0 | 45.0 |
Input | 3.0 | 5.0 | 6.0 |
MainClass | 1.0 | 1.0 | 1.0 |
Person | 1.0 | 1.0 | 7.0 |
RawRequests | 1.1666666666666667 | 2.0 | 7.0 |
Scheduler | 2.6666666666666665 | 6.0 | 8.0 |
WaitRequestMap | 1.8181818181818181 | 4.0 | 20.0 |
Total | 94.0 | ||
Average | 2.292682926829268 | 4.571428571428571 | 13.428571428571429 |
可以看到,我的第二次作业复杂度高的地方和第一次特别相似,都是Elevator中的getIn和go这两个方法,这次我换成了LOOK算法,为了追求尽早调试算法以及完成作业,我在很多地方还是沿用了上次作业的构思,所以没有来得及将其再分解。
顺序图
BUG分析
本次作业均未出现BUG(包括别人的。。。)
心得体会
这次作业虽然是重构的,但是有了第一次作业的基础,主要是有了整体架构的方向,以及对线程问题的更深理解,所花费的时间不算太多,并且也有不错的效果,强测98+,对于使用平均分配的调度策略的我来说着实意外。更加体会到了好架构的偌大帮助,要是第一次作业架构写得好,主要是像我第二次作业这样将共享对象清晰地拎出来,那么这次作业一个小时不到就能完成。
LOOK算法是真的比ALS香太多了,思路不是特别困难,却又能拿高分的方法谁不馋呢(x
第三次作业
同步块的设置和锁的选择
第三次作业我几乎完全沿用了第二次作业,所以共享对象的设置以及加锁方法的配备几乎完全相同。
第三次作业依旧没有出现线程安全问题,这也再次证明了我这个思路的可行性与可靠性。
调度器设计
这次作业的主要改动就在于调度器,对于在第二次作业中已经实现调度器的同学来说十分简单,对于没有实现调度器的同学来说工作量稍高。(对于不换乘)
我这次作业中调度的思路主要还是平均分,虽然说电梯有型号之分,实则AB型号的效率差不了太多,主要是C快,但也不是快了一个数量级,所以我大体依旧采用平均分的思想:满足C的需求平均分到AC型号的电梯中,满足B的需求平均分到AB型号的电梯中。
我并没有采用换乘,实现换乘就要多加几个对共享对象的操作,实际上也不是特别困难,但是我并没有理解到换乘的必要性,我感觉换乘的性能并不会高出特别多,导致实现成本与预期收入的不匹配,我就没有实现换乘了。但平心而论,换乘还是比较考验我们对于线程交互的理解的,或许设置一个必要的换乘作为通过条件还是可以接受的,没有换乘感觉少了一大块需要思考的,希望下一届还是出一个较低的换乘的门槛。
如果说电梯内部的调度也属于这一范畴的话:我第三次作业中电梯内部的调度沿用了第二次作业的LOOK算法。
分析图及可扩展性
类关系图
复杂度统计
method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
Elevator.arrive() | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.closeDoor() | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.Elevator(int,String,String,WaitRequestMap) | 7.0 | 1.0 | 4.0 | 6.0 |
Elevator.getIn() | 33.0 | 6.0 | 14.0 | 16.0 |
Elevator.getOut() | 3.0 | 1.0 | 3.0 | 3.0 |
Elevator.getSize(HashMap>) | 1.0 | 1.0 | 2.0 | 2.0 |
Elevator.go() | 20.0 | 8.0 | 4.0 | 9.0 |
Elevator.openDoor() | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.run() | 1.0 | 1.0 | 2.0 | 2.0 |
Elevator.runRandom() | 13.0 | 5.0 | 6.0 | 6.0 |
Elevator.setDesFloor() | 10.0 | 1.0 | 6.0 | 6.0 |
Input.Input(RawRequests,ElevatorInput,ArrayList,Scheduler) | 0.0 | 1.0 | 1.0 | 1.0 |
Input.run() | 9.0 | 3.0 | 6.0 | 6.0 |
MainClass.main(String[]) | 0.0 | 1.0 | 1.0 | 1.0 |
Person.getArriveFloor() | 0.0 | 1.0 | 1.0 | 1.0 |
Person.getDirection() | 0.0 | 1.0 | 1.0 | 1.0 |
Person.getId() | 0.0 | 1.0 | 1.0 | 1.0 |
Person.getLeaveFloor() | 0.0 | 1.0 | 1.0 | 1.0 |
Person.inToString() | 0.0 | 1.0 | 1.0 | 1.0 |
Person.outToString() | 0.0 | 1.0 | 1.0 | 1.0 |
Person.Person(int,int,int) | 0.0 | 1.0 | 1.0 | 1.0 |
RawRequests.addRequest(Person) | 0.0 | 1.0 | 1.0 | 1.0 |
RawRequests.getRequest() | 2.0 | 2.0 | 2.0 | 2.0 |
RawRequests.isEmpty() | 0.0 | 1.0 | 1.0 | 1.0 |
RawRequests.isOver() | 0.0 | 1.0 | 1.0 | 1.0 |
RawRequests.RawRequests() | 0.0 | 1.0 | 1.0 | 1.0 |
RawRequests.setOver() | 0.0 | 1.0 | 1.0 | 1.0 |
Scheduler.addWaitRequestMap(WaitRequestMap) | 0.0 | 1.0 | 1.0 | 1.0 |
Scheduler.putPerson(Person) | 23.0 | 9.0 | 11.0 | 11.0 |
Scheduler.run() | 17.0 | 4.0 | 7.0 | 7.0 |
Scheduler.Scheduler(ArrayList,RawRequests) | 0.0 | 1.0 | 1.0 | 1.0 |
WaitRequestMap.elevatorIsEmpty() | 3.0 | 3.0 | 2.0 | 3.0 |
WaitRequestMap.floorIsEmpty(int) | 0.0 | 1.0 | 1.0 | 1.0 |
WaitRequestMap.getEleType() | 0.0 | 1.0 | 1.0 | 1.0 |
WaitRequestMap.getMaxFloor(int) | 5.0 | 1.0 | 3.0 | 5.0 |
WaitRequestMap.getMinFloor(int) | 5.0 | 1.0 | 3.0 | 5.0 |
WaitRequestMap.getRequestList(int) | 0.0 | 1.0 | 1.0 | 1.0 |
WaitRequestMap.getWaitMapId() | 0.0 | 1.0 | 1.0 | 1.0 |
WaitRequestMap.isOver() | 0.0 | 1.0 | 1.0 | 1.0 |
WaitRequestMap.putRequest(Person) | 0.0 | 1.0 | 1.0 | 1.0 |
WaitRequestMap.removeRequest(int,Person) | 0.0 | 1.0 | 1.0 | 1.0 |
WaitRequestMap.setOver() | 0.0 | 1.0 | 1.0 | 1.0 |
WaitRequestMap.WaitRequestMap(int,String) | 1.0 | 1.0 | 2.0 | 2.0 |
Total | 153.0 | 75.0 | 104.0 | 118.0 |
Average | 3.558139534883721 | 1.744186046511628 | 2.4186046511627906 | 2.744186046511628 |
class | OCavg | OCmax | WMC |
---|---|---|---|
Elevator | 4.454545454545454 | 13.0 | 49.0 |
Input | 3.0 | 5.0 | 6.0 |
MainClass | 1.0 | 1.0 | 1.0 |
Person | 1.0 | 1.0 | 7.0 |
RawRequests | 1.1666666666666667 | 2.0 | 7.0 |
Scheduler | 4.25 | 9.0 | 17.0 |
WaitRequestMap | 1.75 | 4.0 | 21.0 |
Total | 108.0 | ||
Average | 2.511627906976744 | 5.0 | 15.428571428571429 |
这次作业与前两次作业相比,多出了Scheduler类中的putPerson和run两个复杂度较高的方法,前者是直接将分配算法写在了里面,后者是因为判断的时候if写多了点。
其实也是因为没有下一次作业了,不然还是将调度算法单独拎出来写比较合理。
顺序图
需要注意的一点是:我是采用一个电梯配备一个等待队列,所以电梯和调度器共享的是等待队列数组。
BUG分析
本次作业也均未出现BUG。
心得体会
第三次作业是OO目前所有作业中耗时最少的作业(包括博客作业),我当时仅花了一个小时就写完了,深刻体会到了OO作业不用肝的快乐,希望以后多来点(x)。
线程安全:从第一次作业的痛苦搭建过程中,我就意识到了线程安全就像是一个时刻盯着你的猛虎,所以从第二次作业开始我就采用了比较好的方法来解决线程安全问题:主要是拎出共享对象,将涉及共享对象的操作写到一个类中再思考加锁与否。
层次化:这个单元的层次结构实际上是比较清晰的,主要是根据线程、共享对象来划分层次,这个单元的线程实际上不算特别多,因此层次是比较分明的,主要是:输入➡调度器➡电梯,甚至都是单向的需求流动,所以层次结构更加清晰。
相比于第一单元,这个单元的体验更佳。第一单元是越来越难,第二单元是越来越简单,这就使得我对于第二单元的知识掌握得更加到位。(当然或许第一单元只是让我们快速入门Java)
这个单元中自认为略有不足的地方主要在于找BUG,并没有一种很好的方法去找别人的BUG,使得屋子里面都是风平浪静,可能就丢失了一部分的活跃分。