程序结构分析
第一次作业
第二次作业
第三次作业
度量分析
规模分析
复杂度分析
可拓展性分析
UML
Bug分析与测试策略
Bug分析
测试策略
心得体会
设计要求
模拟多线程实时电梯系统,五个楼座分别有且只有一部纵向电梯,处理同一楼座的乘客请求。
同步块设置与锁的选择
设置共享队列总请求队列waitQueue与各楼层请求队列processingQueue。对于代码中涉及到共享对象的部分,选择使用 synchronized 加锁。当然还有一种奇怪的判断方式在于,有的地方感觉不必加锁,但是跑起来会报错,干脆一同加上。
调度器设计
在第一次作业中似乎没有什么调度可言,由于每个楼座只有一部电梯,我们只需要把对应的请求放入到相应的楼座内,剩下的交给电梯自己取需求即可。
调度策略
电梯调度策略采用look策略,概括而言就是能捎带尽量捎带,性能上也还尚可,就未作进一步优化。
设计要求
在第一次作业的基础上,增加了横向电梯设置以及新增电梯设置。
同步块设置与锁的选择
同步块设置和第一次作业没什么本质差别,增加了各楼层的共享队列floorQueue。涉及共享对象的部分同样采用synchronized 加锁。由于并未进行重构,所以整体和第一次作业差别不大。
调度器设计
在第二次作业中,由于每个楼座中不止存在一个电梯,所以作业一中的分配策略就无法应对了。在编写调度之前也同其他同学进行了讨论,有的同学采用了算法分配,有的采用自由竞争,而我由于看到了指导书中提到 “在电梯的调度上,采用一种较为均衡的调度方式” ,于是采用算法分配。
在请求进入后先由调度器分配到对应的楼座/楼层队列中,然后在队列中进行二次分配,假如A 座 7 层有5个乘客,3部电梯,那么第1个乘客分配给第1部电梯,第2个乘客分配给第2部电梯,第3个乘客分配给第3部电梯,第4个乘客分配给第1部电梯,第5个乘客分配给第2部电梯。
这样的设计最大的优势是设计简单,不需要进行较多的代码编写与重构设计,只需要依靠上次作业的架构并简单添加即可。劣势则在于简单的分配策略并不能保证性能的要求,可能会在性能分上有所损失。
调度策略
纵向电梯依旧采用look策略,不再赘述。而横向电梯的设计策略比较费脑筋,在直观感觉上,哪怕是始终朝着同一个方向移动,由于是环形电梯且一共只有五个楼座,感觉性能上也不会损失太多。但是不设计调度策略总归感觉不太好,于是开始coding......设计出来的策略如下,在电梯内有乘客时,向乘客方向移动,同时实现同方向捎带;没有乘客时则向最近的一个方向运行。
设计要求
在前两次作业基础上,增加换乘请求。同时横向电梯选择性停靠,并不一定能够到达所有楼座。
同步块设置与锁的选择
同上
调度器设计
调度器设计整体与第二次作业相同,先由调度器分配到楼层/楼座中后再分配到各个电梯。但是由于设计要求的变化也做出了一定改变。
首先是对于横向电梯,由于并不一定能到达每个楼座,所以也就不能简单的采用平均分配策略,因为你分配的电梯可能根本送不到对应楼层。这时就需要进行特判,将请求分配给能够送到的电梯中。
但是这样又产生了另一个问题,平均分配策略失效了,因为存在特判,也就并不能保证同一楼层的电梯分配乘客数量的均衡。这时本人采用了比较分配的方法,再满足送达条件的情况下,优先将请求分配给请求数量较少的电梯,从而达到分配的相对均衡。
转乘设计
第三次作业中最核心的问题就在于转乘问题,本人在设计之初试图继续沿用以往的设计框架进行构造,但是发现实在无法实现,于是重写了PersonRequest类,增加了多个属性,如transfer属性用以标志是否需要转乘,transfering属性用以标志是否正在转乘。
一个需要转乘的乘客路径大致可分为三段,初始楼座上下运行,转乘电梯横向运行,目标楼座上下运行。因此可以进行设计,先在初始楼座运行到转乘楼层,在由转乘电梯运送到目标楼座,最后在目标楼座内运送到目标楼层即可。
这是宏观的设计,具体可能还有一些特殊的请求,比如转乘楼层即是目标楼层,那么本质上只有两段路要走等等,特殊处理即可。
可以看到,代码行数达到了一千二百行,可以说是一个较为庞大的数字了。主要行数贡献的类为RequestQueue类,该类中定义了横向,纵向调度策略,由于判定较为复杂从而导致了行数较多。其次是Elevator类和Conveyor类,代表了纵向电梯和横向电梯,其中的开关门判断,进人出人也使用了较多行数。
class | OCavg | OCmax | WMC |
---|---|---|---|
Channel | 4.2 | 11.0 | 21.0 |
Conveyor | 2.9444444444444446 | 10.0 | 53.0 |
Elevator | 2.6956521739130435 | 12.0 | 62.0 |
Input | 4.125 | 10.0 | 33.0 |
MainClass | 8.0 | 8.0 | 8.0 |
MyRequest | 1.0454545454545454 | 2.0 | 23.0 |
OutputThread | 1.0 | 1.0 | 3.0 |
RequestQueue | 3.347826086956522 | 14.0 | 77.0 |
Total | 280.0 | ||
Average | 2.7184466019417477 | 8.5 | 35.0 |
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
RequestQueue.setTransferEnd(boolean) | 0.0 | 1.0 | 1.0 | 1.0 |
RequestQueue.setReqNumber(int) | 0.0 | 1.0 | 1.0 | 1.0 |
RequestQueue.setPersonRequests(ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
RequestQueue.setMark(int) | 0.0 | 1.0 | 1.0 | 1.0 |
RequestQueue.setInputEnd(boolean) | 0.0 | 1.0 | 1.0 | 1.0 |
RequestQueue.setEleNumber(int) | 0.0 | 1.0 | 1.0 | 1.0 |
RequestQueue.RequestQueue() | 1.0 | 1.0 | 2.0 | 2.0 |
RequestQueue.noty() | 0.0 | 1.0 | 1.0 | 1.0 |
RequestQueue.isTransferEnd() | 0.0 | 1.0 | 1.0 | 1.0 |
RequestQueue.isInputEnd() | 0.0 | 1.0 | 1.0 | 1.0 |
RequestQueue.isEmpty() | 0.0 | 1.0 | 1.0 | 1.0 |
RequestQueue.getReqNumber() | 0.0 | 1.0 | 1.0 | 1.0 |
RequestQueue.getPersonRequests() | 0.0 | 1.0 | 1.0 | 1.0 |
RequestQueue.getOneRequest() | 6.0 | 2.0 | 4.0 | 6.0 |
RequestQueue.getMin() | 3.0 | 1.0 | 2.0 | 3.0 |
RequestQueue.getMark() | 0.0 | 1.0 | 1.0 | 1.0 |
RequestQueue.getEleRequests(int, int, int, boolean) | 42.0 | 1.0 | 23.0 | 24.0 |
RequestQueue.getEleNumber() | 0.0 | 1.0 | 1.0 | 1.0 |
RequestQueue.getConRequests(int, int, int, boolean) | 42.0 | 1.0 | 23.0 | 24.0 |
RequestQueue.eleElse(int, int, boolean) | 30.0 | 1.0 | 18.0 | 18.0 |
RequestQueue.dis(MyRequest) | 0.0 | 1.0 | 1.0 | 1.0 |
RequestQueue.conElse(int, int, boolean) | 30.0 | 1.0 | 18.0 | 18.0 |
RequestQueue.addRequest(MyRequest) | 4.0 | 1.0 | 5.0 | 5.0 |
OutputThread.println(String) | 0.0 | 1.0 | 1.0 | 1.0 |
OutputThread.OutputThread() | 0.0 | 1.0 | 1.0 | 1.0 |
OutputThread.getInstance() | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.toString() | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.setTransferring(boolean) | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.setTransfer(boolean) | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.setToFloor(int) | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.setToBuilding(char) | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.setPersonId(int) | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.setFromFloor(int) | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.setFromBuilding(char) | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.MyRequest(int, int, char, char, int, boolean, int, ...) | 3.0 | 1.0 | 1.0 | 3.0 |
MyRequest.isTransferring() | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.isTransfer() | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.getTransferId() | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.getTransferFloor() | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.getToFloor() | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.getToBuilding() | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.getPersonId() | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.getInitFloor() | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.getInitBuilding() | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.getFromFloor() | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.getFromBuilding() | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.getEndFloor() | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.getEndBuilding() | 0.0 | 1.0 | 1.0 | 1.0 |
MainClass.main(String[]) | 10.0 | 1.0 | 4.0 | 8.0 |
Input.run() | 30.0 | 3.0 | 12.0 | 13.0 |
Input.newReq(Request, boolean, int) | 1.0 | 1.0 | 2.0 | 2.0 |
Input.newEle(RequestQueue, Request) | 0.0 | 1.0 | 1.0 | 1.0 |
Input.newCon(RequestQueue, Request) | 0.0 | 1.0 | 1.0 | 1.0 |
Input.Input(RequestQueue, ArrayList, ArrayList, ArrayList, ArrayList, RequestQueue) | 0.0 | 1.0 | 1.0 | 1.0 |
Input.ifTransfer(ArrayList, PersonRequest) | 12.0 | 6.0 | 6.0 | 7.0 |
Input.getTransferId(ArrayList, PersonRequest) | 21.0 | 6.0 | 10.0 | 10.0 |
Input.findConveyor(ArrayList, int) | 3.0 | 3.0 | 3.0 | 3.0 |
Elevator.setPersons(RequestQueue) | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.setNumber(int) | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.setMain() | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.setFloor(int) | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.setElevatorQueue(RequestQueue) | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.setDirection(int) | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.setBalcony(char) | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.run() | 29.0 | 3.0 | 16.0 | 22.0 |
Elevator.personDir(MyRequest) | 1.0 | 2.0 | 1.0 | 2.0 |
Elevator.peopleOut(RequestQueue) | 26.0 | 7.0 | 9.0 | 10.0 |
Elevator.peopleIn(ArrayList) | 25.0 | 1.0 | 8.0 | 10.0 |
Elevator.needOpen(ArrayList, RequestQueue) | 13.0 | 5.0 | 6.0 | 9.0 |
Elevator.move() | 6.0 | 1.0 | 5.0 | 5.0 |
Elevator.hasSameDir(ArrayList, MyRequest) | 10.0 | 4.0 | 7.0 | 8.0 |
Elevator.getPersons() | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.getNumber() | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.getFloor() | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.getElevatorQueue() | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.getDirection() | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.getBalcony() | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.Elevator(RequestQueue, char, int, int, double, int, RequestQueue, ...) | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.doorOpen() | 1.0 | 1.0 | 2.0 | 2.0 |
Elevator.doorClose() | 1.0 | 1.0 | 2.0 | 2.0 |
Conveyor.setMessage(int) | 0.0 | 1.0 | 1.0 | 1.0 |
Conveyor.run() | 24.0 | 3.0 | 16.0 | 18.0 |
Conveyor.personDir(MyRequest) | 2.0 | 2.0 | 2.0 | 3.0 |
Conveyor.peopleOut(RequestQueue) | 19.0 | 6.0 | 7.0 | 7.0 |
Conveyor.peopleIn(ArrayList) | 27.0 | 1.0 | 10.0 | 12.0 |
Conveyor.needOpen(ArrayList, RequestQueue) | 13.0 | 5.0 | 6.0 | 9.0 |
Conveyor.move() | 6.0 | 1.0 | 5.0 | 5.0 |
Conveyor.hasSameDir(ArrayList, MyRequest) | 10.0 | 4.0 | 7.0 | 8.0 |
Conveyor.getPersons() | 0.0 | 1.0 | 1.0 | 1.0 |
Conveyor.getNumber() | 0.0 | 1.0 | 1.0 | 1.0 |
Conveyor.getMessage() | 0.0 | 1.0 | 1.0 | 1.0 |
Conveyor.getIndex() | 0.0 | 1.0 | 1.0 | 1.0 |
Conveyor.getFloor() | 0.0 | 1.0 | 1.0 | 1.0 |
Conveyor.getElevatorQueue() | 0.0 | 1.0 | 1.0 | 1.0 |
Conveyor.doorOpen() | 1.0 | 1.0 | 2.0 | 2.0 |
Conveyor.doorClose() | 1.0 | 1.0 | 2.0 | 2.0 |
Conveyor.dis(MyRequest) | 0.0 | 1.0 | 1.0 | 1.0 |
Conveyor.Conveyor(RequestQueue, int, int, int, double, int, int, ...) | 0.0 | 1.0 | 1.0 | 1.0 |
Channel.setEnd(boolean) | 0.0 | 1.0 | 1.0 | 1.0 |
Channel.run() | 29.0 | 7.0 | 12.0 | 13.0 |
Channel.isEnd() | 2.0 | 2.0 | 2.0 | 3.0 |
Channel.Channel(RequestQueue, ArrayList, ArrayList, ArrayList, ArrayList, RequestQueue) | 0.0 | 1.0 | 1.0 | 1.0 |
Channel.balconyAdd(MyRequest) | 5.0 | 1.0 | 6.0 | 6.0 |
Total | 489.0 | 156.0 | 333.0 | 371.0 |
Average | 4.747 | 1.514 | 3.233 | 3.601 |
电梯属性(编号,载客量,速度,方向等)都设置为类内属性,可以较好的满足电梯自定义的要求。
电梯行为(开关门,乘客进出,移动)都分别设计为独立方法,调用清晰。
乘客请求也重新设置了新的类而非使用官方PersonRequest类,满足后续设计要求。
第一次作业中强测出现了一个RTLE错误,经查是由于采用的调度策略的设计缺陷造成,在所提交的代码中,在处理类似
1-FROM-C-5-TO-C-1
2-FROM-C-6-TO-C-1
3-FROM-C-7-TO-C-1
4-FROM-C-8-TO-C-1
5-FROM-C-9-TO-C-1
6-FROM-C-10-TO-C-1
的样例时,会先到五楼,然后送到一楼,再到六楼,然后送到一楼......没有能实现尽可能捎带的功能。
第二次作业在强测与互测中均未出现错误。
第三次作业则出现了许多的错误。首先是CTLE错误,由于在同一楼座内存在多个电梯时,会出现某一电梯在waiting而另一电梯在运行时发出了notify信号从而打断waiting造成轮询。第二时一个WA,这是由于在最后一个请求较为特殊时,就会出现奇怪的错误,由于先执行peopleout,再添加到等待队列,会导致最后一段纵向运行无法得到该请求从而送不到目的地。第三是最玄学的RTLE错误,我改了又改看了又看,忙活了好几天也并未解决这一问题,最后仍然有两三个点会出现RTLE,而且每次不固定,可谓是按下葫芦浮起瓢,弄得心力交瘁,实在改不出来就摆烂了,扣分也没办法了。
在本单元的测试中,大致可分为两种方法,一种是暴力生成数据直接取跑其他同学的代码进行盲目性Hack,另一种是通过阅读代码构造边界数据,特殊数据进行目的性Hack,也就是所谓的黑盒测试和白盒测试。
在第一次作业中的测试数据都是自动生成的直接拿来用,然而hack效果不错,后来发现是输出线程不安全的问题,即使输出结果无误,输出顺序出现问题一样是WA。
第二次作业大家似乎都做得很好,在经过第一次作业的debug,第二次作业仅仅增加了横向电梯改动不大,造的数据也基本hack不到人。
第三次作业问题出现的就很多了,基本随便生成的数据都会hack到同学,这主要是因为第三次作业中的设计繁杂,细枝末节之处易出各种BUG,WA,CTLE,RTLE层出不穷,所以也就仅仅采用了自动生成数据暴力测试策略。
老师在课上说:第二单元比第一单元简单些。但是实际感觉似乎并不符合,第一单元的困难主要在于从0-1的过程,从一个并未接触OO的情形直接面对作业的困难;而第二单元的困难更多体现于知识难度上,任务复杂上,无论是锁的问题,多线程的调试,复杂的转乘策略,无法复现的BUG都给我带来了巨大的挑战,尤其是第三次作业的难度,bug多而杂,实在是困难重重。
线程安全问题在多线程电梯的设计过程中理解加深,如何处理共享资源,如何加锁都给我带来了不小的收获。
总而言之言而总之,无论多么困难的任务总归都已经过去了,总结经验,面向下一单元的学习才是更加重要的事情了。