写在前面
OO课程已经过半,过去的第二单元主要是训练了我们的多线程设计,以电梯为载体,步步深入,层层递进。本单元我学到了:
- 如何做一个线程安全的设计。
- 如何去合理地使用Java的锁机制。
下面我将以三次作业为例,具体谈一下我的收获。
第五次作业
这次作业的要求是写一个傻瓜式调度的电梯,笔者也是按指导书去写的,整体写起来很轻松,也算入门了Java多线程的写法、共享对象的使用以及锁的使用。
代码规模
类图
UML
第一次的业务逻辑很清晰,就是简单的生产者-消费者模型,输入类为生产者,电梯类为消费者,共享对象是调度器里的请求队列。可以很简单地实现傻瓜电梯的功能,也不容易出bug。
复杂度分析
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
elevator.Elevator.Elevator(Scheduler) | 1 | 1 | 1 |
elevator.Elevator.moveToFloor(int) | 1 | 2 | 2 |
elevator.Elevator.personIn(int) | 1 | 2 | 2 |
elevator.Elevator.personOut(int) | 1 | 2 | 2 |
elevator.Elevator.run() | 3 | 3 | 3 |
elevator.Main.main(String[]) | 1 | 1 | 1 |
elevator.RequestIn.RequestIn(Scheduler) | 1 | 1 | 1 |
elevator.RequestIn.run() | 3 | 4 | 4 |
elevator.Scheduler.Scheduler() | 1 | 1 | 1 |
elevator.Scheduler.addRequest(PersonRequest) | 1 | 1 | 1 |
elevator.Scheduler.getQueue() | 1 | 1 | 3 |
elevator.Scheduler.getRequest() | 1 | 3 | 3 |
Class | OCavg | WMC | |
elevator.Elevator | 1.4 | 7 | |
elevator.Main | 1 | 1 | |
elevator.RequestIn | 2 | 4 | |
elevator.Scheduler | 1.75 | 7 |
可以看到第一次业务逻辑简单,代码的复杂度也相对小一点,只有电梯的运行方法稍显复杂,这也是情理之中的。
SOLID原则分析
本单元作业笔者未使用继承和接口,所以以下只分析SRP和OCP。
单一责任原则(SRP)
笔者的设计符合SRP原则,输入类只负责输入,电梯只负责运行,调度器只负责存储请求队列。
开放封闭原则(OCP)
笔者在第一次作业中暂未考虑扩展性问题。
Bug分析
公测
笔者的程序在公测中未被发现bug。
互测
笔者的程序在互测中未被发现bug。
测试方法
随机生成测试数据,用java程序按时间点向电梯输入请求,得到输出后用python程序检查最终的电梯状态和人员状态,看一下调度是否正确。
第六次作业
本次作业的要求是写一个ALS调度算法的电梯,由于这次作业加入了性能分评测,故笔者写的电梯是Look调度算法。这次作业的逻辑稍显复杂,怎么去避免死锁发生,怎么去写一个线程安全的架构是我们考虑的重点。
代码规模
类图
UML
第二次作业相比第一次作业,只增加了捎带需求,而为了适应多电梯的扩展,我对电梯系统做了重构,首先,增加Elevatorbuild类,负责生成多部电梯,然后Elevator按Look调度算法运行,每到一层楼都和Scheduler调度器进行一次交互,进行接送乘客。因为是单电梯,所以本次的调度器职责就是把收到的指令分配给电梯。
复杂度分析
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
Main.main(String[]) | 1 | 1 | 1 |
elevatorsystem.RequestInput.RequestInput(Scheduler) | 1 | 1 | 1 |
elevatorsystem.RequestInput.run() | 3 | 4 | 4 |
elevatorsystem.Scheduler.addRequest(PersonRequest) | 1 | 1 | 1 |
elevatorsystem.Scheduler.getScheduler() | 1 | 1 | 3 |
elevatorsystem.Scheduler.setRunning(Boolean) | 1 | 1 | 1 |
elevatorsystem.elevator.Elevator.Elevator() | 1 | 1 | 1 |
elevatorsystem.elevator.Elevator.check() | 1 | 6 | 8 |
elevatorsystem.elevator.Elevator.close() | 1 | 2 | 2 |
elevatorsystem.elevator.Elevator.doWait() | 1 | 2 | 2 |
elevatorsystem.elevator.Elevator.elevatorStop() | 1 | 6 | 11 |
elevatorsystem.elevator.Elevator.getArrival() | 1 | 1 | 1 |
elevatorsystem.elevator.Elevator.getDirection() | 1 | 1 | 1 |
elevatorsystem.elevator.Elevator.getFloor() | 1 | 1 | 1 |
elevatorsystem.elevator.Elevator.getNextRequest() | 1 | 4 | 4 |
elevatorsystem.elevator.Elevator.getRequestNum() | 1 | 1 | 1 |
elevatorsystem.elevator.Elevator.getRunning() | 1 | 1 | 1 |
elevatorsystem.elevator.Elevator.getStatus() | 1 | 1 | 1 |
elevatorsystem.elevator.Elevator.isDown(PersonRequest) | 1 | 1 | 1 |
elevatorsystem.elevator.Elevator.isUp(PersonRequest) | 1 | 1 | 1 |
elevatorsystem.elevator.Elevator.load() | 1 | 6 | 6 |
elevatorsystem.elevator.Elevator.lookDown() | 4 | 2 | 4 |
elevatorsystem.elevator.Elevator.lookUp() | 4 | 2 | 4 |
elevatorsystem.elevator.Elevator.moveDown() | 1 | 2 | 3 |
elevatorsystem.elevator.Elevator.moveUp() | 1 | 2 | 3 |
elevatorsystem.elevator.Elevator.open() | 1 | 2 | 2 |
elevatorsystem.elevator.Elevator.run() | 6 | 16 | 17 |
elevatorsystem.elevator.Elevator.setArrival(ArrayList |
1 | 1 | 1 |
elevatorsystem.elevator.Elevator.setDirection(int) | 1 | 1 | 1 |
elevatorsystem.elevator.Elevator.setFloor(int) | 1 | 1 | 1 |
elevatorsystem.elevator.Elevator.setInf(int) | 1 | 1 | 1 |
elevatorsystem.elevator.Elevator.setRequestNum(Integer) | 1 | 1 | 1 |
elevatorsystem.elevator.Elevator.setRunning(Boolean) | 1 | 1 | 1 |
elevatorsystem.elevator.Elevator.setStatus(Integer,ArrayList |
1 | 1 | 1 |
elevatorsystem.elevator.Elevator.setSup(int) | 1 | 1 | 1 |
elevatorsystem.elevator.Elevator.unload() | 1 | 3 | 3 |
elevatorsystem.elevator.ElevatorBuild.InitOne() | 3 | 2 | 3 |
elevatorsystem.elevator.ElevatorBuild.getElevatorBuild() | 1 | 1 | 3 |
elevatorsystem.elevator.ElevatorBuild.getElevatorOne() | 1 | 1 | 1 |
elevatorsystem.elevator.ElevatorBuild.run() | 1 | 1 | 1 |
Class | OCavg | WMC | |
Main | 1 | 1 | |
elevatorsystem.RequestInput | 2 | 4 | |
elevatorsystem.Scheduler | 1.67 | 5 | |
elevatorsystem.elevator.Elevator | 2.17 | 65 | |
elevatorsystem.elevator.ElevatorBuild | 2 | 8 |
不出所料,仍然是Elevator的运行方法和每层楼的接送乘客方法复杂度较高。
SOLID原则分析
单一责任原则(SRP)
笔者的设计符合SRP原则,输入类只负责输入,电梯的Builder类只负责生成电梯,电梯只负责运行已收到的请求,调度器只负责将收到的请求分给电梯。
开放封闭原则(OCP)
笔者在这次作业中充分考虑了向多电梯的可扩展性,新增ElevatorBuild类,负责根据不同参数生成不同种类的电梯,所以这个架构在第三次作业中得以沿用。
Bug分析
公测
笔者的程序在公测中未被发现bug。
互测
笔者的程序在互测中未被发现bug。笔者发现其他同学的一个bug,在某种情况下,他的电梯会一直向上或向下运行而不会停下(所谓的飞天遁地),原因应该是对边界楼层和电梯转向的处理不严密。
测试方法
笔者沿用了第一次作业的测试工具,定时投放+输出数据正确性检测。
第七次作业
最后一次作业增加了多电梯(3个),并且每个电梯的可到达楼层不同,需要考虑换乘的情况,整体难度较大,并且对线程稳定性的要求也很高。笔者的设计是采用三台Look调度算法的电梯,每个电梯只负责自己请求队列里的指令,需要换乘的指令会在指令输入的时候被拆分成两条指令,在第一条指令执行完毕后将第二条指令加入请求队列。
代码规模
类图
UML
可以看到,对于笔者的架构,从第二次作业到第三次作业,几乎只需要修改调度器,其他部分不做改动,所以笔者这次作业完成地也比较轻松。
复杂度分析
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
Main.main(String[]) | 1 | 1 | 1 |
elevatorsystem.RequestInput.RequestInput(Scheduler) | 1 | 1 | 1 |
elevatorsystem.RequestInput.run() | 3 | 4 | 4 |
elevatorsystem.Scheduler.InitChange(ArrayList<ArrayList |
1 | 2 | 2 |
elevatorsystem.Scheduler.Scheduler() | 1 | 1 | 1 |
elevatorsystem.Scheduler.addRequest(PersonRequest) | 1 | 10 | 10 |
elevatorsystem.Scheduler.checkStatus(char,ArrayList |
6 | 9 | 9 |
elevatorsystem.Scheduler.createMapA() | 1 | 5 | 10 |
elevatorsystem.Scheduler.createMapB() | 1 | 5 | 8 |
elevatorsystem.Scheduler.createMapC() | 1 | 5 | 6 |
elevatorsystem.Scheduler.doWait(Object) | 1 | 2 | 2 |
elevatorsystem.Scheduler.getChange() | 1 | 1 | 1 |
elevatorsystem.Scheduler.getLock() | 1 | 1 | 1 |
elevatorsystem.Scheduler.getMap(char) | 3 | 1 | 3 |
elevatorsystem.Scheduler.getQueue(char) | 3 | 1 | 3 |
elevatorsystem.Scheduler.getRequestNum(char) | 3 | 1 | 3 |
elevatorsystem.Scheduler.getRunning() | 1 | 1 | 1 |
elevatorsystem.Scheduler.getScheduler() | 1 | 1 | 3 |
elevatorsystem.Scheduler.lock() | 1 | 1 | 1 |
elevatorsystem.Scheduler.mapInit(HashMap<Integer, ArrayList |
3 | 2 | 3 |
elevatorsystem.Scheduler.removeRequest(PersonRequest) | 1 | 1 | 4 |
elevatorsystem.Scheduler.requestMapInit(HashMap<Integer, ArrayList |
3 | 2 | 3 |
elevatorsystem.Scheduler.setRunning(Boolean) | 1 | 1 | 1 |
elevatorsystem.Scheduler.takeApart(PersonRequest) | 1 | 10 | 10 |
elevatorsystem.Scheduler.unLock() | 1 | 1 | 1 |
elevatorsystem.elevator.Elevator.Elevator(Scheduler,Object) | 1 | 1 | 1 |
elevatorsystem.elevator.Elevator.addAvailableFloor(int) | 1 | 1 | 1 |
elevatorsystem.elevator.Elevator.check() | 1 | 7 | 9 |
elevatorsystem.elevator.Elevator.close() | 1 | 2 | 2 |
elevatorsystem.elevator.Elevator.doWait(Object) | 1 | 2 | 2 |
elevatorsystem.elevator.Elevator.elevatorStop() | 1 | 5 | 10 |
elevatorsystem.elevator.Elevator.getArrival() | 1 | 1 | 1 |
elevatorsystem.elevator.Elevator.getDirection() | 1 | 1 | 1 |
elevatorsystem.elevator.Elevator.getFloor() | 1 | 1 | 1 |
elevatorsystem.elevator.Elevator.getNextRequest() | 1 | 4 | 4 |
elevatorsystem.elevator.Elevator.isDown(PersonRequest) | 1 | 1 | 1 |
elevatorsystem.elevator.Elevator.isUp(PersonRequest) | 1 | 1 | 1 |
elevatorsystem.elevator.Elevator.load() | 3 | 6 | 7 |
elevatorsystem.elevator.Elevator.lookDown() | 4 | 2 | 4 |
elevatorsystem.elevator.Elevator.lookUp() | 4 | 2 | 4 |
elevatorsystem.elevator.Elevator.moveDown() | 1 | 2 | 3 |
elevatorsystem.elevator.Elevator.moveUp() | 1 | 2 | 3 |
elevatorsystem.elevator.Elevator.open() | 1 | 2 | 2 |
elevatorsystem.elevator.Elevator.run() | 6 | 17 | 19 |
elevatorsystem.elevator.Elevator.setArrival(ArrayList |
1 | 1 | 1 |
elevatorsystem.elevator.Elevator.setDirection(int) | 1 | 1 | 1 |
elevatorsystem.elevator.Elevator.setFloor(int) | 1 | 1 | 1 |
elevatorsystem.elevator.Elevator.setInf(int) | 1 | 1 | 1 |
elevatorsystem.elevator.Elevator.setMaxContain(int) | 1 | 1 | 1 |
elevatorsystem.elevator.Elevator.setMoveTime(int) | 1 | 1 | 1 |
elevatorsystem.elevator.Elevator.setName(char) | 1 | 1 | 1 |
elevatorsystem.elevator.Elevator.setOpenTime(int) | 1 | 1 | 1 |
elevatorsystem.elevator.Elevator.setSup(int) | 1 | 1 | 1 |
elevatorsystem.elevator.Elevator.unload() | 1 | 7 | 8 |
elevatorsystem.elevator.ElevatorBuild.ElevatorBuild(Scheduler) | 1 | 1 | 1 |
elevatorsystem.elevator.ElevatorBuild.InitA() | 1 | 3 | 5 |
elevatorsystem.elevator.ElevatorBuild.InitB() | 1 | 3 | 4 |
elevatorsystem.elevator.ElevatorBuild.InitC() | 1 | 3 | 3 |
elevatorsystem.elevator.ElevatorBuild.getElevatorA() | 1 | 1 | 1 |
elevatorsystem.elevator.ElevatorBuild.getElevatorB() | 1 | 1 | 1 |
elevatorsystem.elevator.ElevatorBuild.getElevatorBuild(Scheduler) | 1 | 1 | 3 |
elevatorsystem.elevator.ElevatorBuild.getElevatorC() | 1 | 1 | 1 |
elevatorsystem.elevator.ElevatorBuild.run() | 1 | 1 | 1 |
Class | OCavg | WMC | |
Main | 1 | 1 | |
elevatorsystem.RequestInput | 2 | 4 | |
elevatorsystem.Scheduler | 3.14 | 69 | |
elevatorsystem.elevator.Elevator | 2.45 | 71 | |
elevatorsystem.elevator.ElevatorBuild | 1.89 | 17 |
可以看到,调度器的添加请求方法和拆分请求方法,以及电梯的run方法复杂度很高。结合代码不难看出复杂度高的原因。调度器的添加请求方法需要做这么几件事:
- 判断电梯是否可直达,是否满载,如果未满载并且可直达则加入请求;
- 如果全部满载则选择一部可直达的电梯加入请求;
- 如果不可直达,则调用拆分请求方法进行指令拆分和添加。
这其中有很复杂的条件语句,所以方法复杂度略高。
至于电梯的run方法,是负责整个电梯运行逻辑的,所以复杂度略高也合乎情理。
SOLID原则分析
单一责任原则(SRP)
笔者的设计符合SRP原则,输入类只负责输入,电梯的Builder类只负责生成电梯,电梯只负责运行已收到的请求,调度器只负责接收和拆分请求,并分配给不同的电梯。
开放封闭原则(OCP)
笔者在这次作业中保留了对更多电梯的可扩展性,通过ElevatorBuild类可进行扩展。但是本次作业我的调度器是每个电梯一个请求队列,所以扩展起来比较麻烦,我想如果设计成所有电梯共用一个请求队列的话,应该会更加便于扩展。
Bug分析
公测
本次公测笔者的程序炸了很多点,原因是有一个条件语句加错了位置,更改后可稳定通过全部测试点。
互测
笔者找到其他同学很多bug,有程序无法结束的,有电梯超载的,有运行时间过长的,还有乘客未送到指定位置的。毕竟是C组,这么多bug也就不足为奇了。
总结
多线程设计让我明白了以下几点:
- 一定要充分思考以后再动手写代码,不然在多线程设计中出了bug是很难调试的。代码不规范,debug两行泪。
- 不要盲目地去使用锁,synchronized固然好用,但是也不能滥用,要明确锁的是哪个对象,哪个类。synchronized使用也是有技巧的,有时候一个方法里面只需要锁住一个对象就可以,没必要把整个方法都锁起来,这样会造成效率的降低。
- 每写好一个版本后,都需要对应充分的测试,一个优秀的工程设计师是兼顾正确性和高性能的,不要为了追求一点点的性能而在设计上产生漏洞,更不要目测程序没问题就不去做充分的测试。这是我在第三次作业中学到的,没测试充分,导致bug没被发现,所以炸掉了强测。
希望在以后的学习中,我可以更好地掌握测试方法,写出更加完美的程序。