OO2022第二单元总结
OO2022第二单元总结
本单元内容是电调调度与多线程,因为之前完全没有真正上手写过多线程程序,所以相比第一单元对我而言相对熟悉的内容,完成第二单元需要更多的思考。
在代码的架构设计上,使用了最基本的生产者-消费者模式,简而言之就是一个线程解析请求,然后分发到各个电梯的请求队列。
第一二次作业请求解析与分发都和输入处理在一个线程,严格来讲没有控制器,第三次作业由于采用了流水线模式,部分完成的请求需要再次分发以完成下一阶段,所以增加了控制器线程。
在调度策略上,采用的是能想到的最简单的方法,即纵向电梯采用Look算法,横向电梯只朝一个方向运动,第三次作业拆分使用基准策略的三段拆分。
第一次作业
概述
这一次作业相对较为简单,困难可能在于对多线程还不太熟悉。
最开始让所有电梯共用一个请求队列,导致频繁的上锁操作(有一个请求来了,所有电梯都被notify,都去看一眼),导致CPU超时,于是改成每个电梯一个队列。(也有可能是Java8的stream在小规模数据下效率相对循环偏低?但是之后的作业里仍然使用大量stream,好像也没出现问题)
UML
类图
注:UML图中的公开字段代表有getter和setter的字段
注2:UML图中仅包含关键字段和方法(感觉一堆getter,setter放在里面意义不大,不能让人得出任何额外信息,而且画太多了mermaid会出分辨率问题)
协作图
如上文所说,使用了最基本的生产者-消费者模式,一个线程解析请求,然后分发到各个电梯的请求队列。
OutputWrapper
通过给输出加锁解决了输出线程安全问题。
度量值
Project Name | Package Name | Type Name | MethodName | LOC | CC | PC |
---|---|---|---|---|---|---|
proj5 | (default package) | Elevator | Elevator | 7 | 1 | 4 |
proj5 | (default package) | Elevator | getBuilding | 3 | 1 | 0 |
proj5 | (default package) | Elevator | getLastInvokeTime | 3 | 1 | 0 |
proj5 | (default package) | Elevator | setLastInvokeTime | 3 | 1 | 1 |
proj5 | (default package) | Elevator | getElevatorState | 3 | 1 | 0 |
proj5 | (default package) | Elevator | setElevatorState | 3 | 1 | 1 |
proj5 | (default package) | Elevator | getRequestQueue | 3 | 1 | 0 |
proj5 | (default package) | Elevator | getCapacity | 3 | 1 | 0 |
proj5 | (default package) | Elevator | getCarrying | 3 | 1 | 0 |
proj5 | (default package) | Elevator | setCarrying | 3 | 1 | 1 |
proj5 | (default package) | Elevator | getFloor | 3 | 1 | 0 |
proj5 | (default package) | Elevator | setFloor | 3 | 1 | 1 |
proj5 | (default package) | Elevator | openDoor | 4 | 1 | 0 |
proj5 | (default package) | Elevator | beginCloseDoor | 4 | 1 | 0 |
proj5 | (default package) | Elevator | endCloseDoor | 5 | 1 | 0 |
proj5 | (default package) | Elevator | invitePerson | 5 | 1 | 1 |
proj5 | (default package) | Elevator | moveUp | 4 | 1 | 0 |
proj5 | (default package) | Elevator | moveDown | 4 | 1 | 0 |
proj5 | (default package) | Elevator | dropPerson | 4 | 1 | 1 |
proj5 | (default package) | Elevator | upFloor | 4 | 1 | 0 |
proj5 | (default package) | Elevator | downFloor | 4 | 1 | 0 |
proj5 | (default package) | Elevator | arrive | 5 | 1 | 0 |
proj5 | (default package) | InputHandler | InputHandler | 4 | 1 | 2 |
proj5 | (default package) | InputHandler | run | 18 | 3 | 0 |
proj5 | (default package) | LookElevator | LookElevator | 3 | 1 | 4 |
proj5 | (default package) | LookElevator | run | 48 | 15 | 0 |
proj5 | (default package) | LookElevator | handleMoving | 22 | 4 | 0 |
proj5 | (default package) | LookElevator | handleClosing | 25 | 6 | 1 |
proj5 | (default package) | LookElevator | handleOpening | 27 | 6 | 1 |
proj5 | (default package) | LookElevator | handleClosed | 27 | 8 | 2 |
proj5 | (default package) | Main | main | 22 | 4 | 1 |
proj5 | (default package) | OutputWrapper | println | 3 | 1 | 1 |
proj5 | (default package) | Request | Request | 7 | 1 | 5 |
proj5 | (default package) | Request | Request | 7 | 1 | 1 |
proj5 | (default package) | Request | getFromBuilding | 3 | 1 | 0 |
proj5 | (default package) | Request | getToBuilding | 3 | 1 | 0 |
proj5 | (default package) | Request | getFromFloor | 3 | 1 | 0 |
proj5 | (default package) | Request | getToFloor | 3 | 1 | 0 |
proj5 | (default package) | Request | getId | 3 | 1 | 0 |
proj5 | (default package) | RequestQueue | RequestQueue | 4 | 1 | 0 |
proj5 | (default package) | RequestQueue | getRequest | 3 | 1 | 0 |
proj5 | (default package) | RequestQueue | watchForRequest | 8 | 1 | 0 |
proj5 | (default package) | RequestQueue | addRequest | 4 | 1 | 1 |
proj5 | (default package) | RequestQueue | removeRequest | 4 | 1 | 1 |
proj5 | (default package) | RequestQueue | isEmpty | 3 | 1 | 0 |
proj5 | (default package) | RequestQueue | isFinish | 3 | 1 | 0 |
proj5 | (default package) | RequestQueue | setNoMoreInput | 4 | 1 | 1 |
proj5 | (default package) | Scheduler | Scheduler | 3 | 1 | 1 |
proj5 | (default package) | Scheduler | dispatchRequest | 4 | 1 | 1 |
proj5 | (default package) | Scheduler | setFinished | 5 | 2 | 0 |
复杂度集中在电梯的调度运行策略上(Look算法),确实问题大多出现在这里。
Bug
电梯在尝试开门接受请求时没有判断是否已满(但是判断了已满则不让乘客进入),导致出现电梯不断开门想让乘客进入但是进不了又关门不断循环直到超时。
第二次作业
概述
这一次作业新增了横向电梯(其实就是横过来的纵向电梯,本质上没有区别)和动态增加电梯请求,相对上一次作业修改不大。
UML
类图
因为之前考虑到了可能会在之后加入更多种类的电梯,于是使用抽象类Elevator
定义了电梯的公共行为,第二次作业加入水平电梯只需要继承上述抽象类即可。
因为不再是只有五部电梯,所以定义ElevatorSlot
描述楼层和楼号信息,使用HashMap<ElevatorSlot,ArrayList<Elevator>>
在找符合请求的电梯时就不需要把所有电梯扫描一遍。(优化效果未知)
协作图
度量值
Project Name | Package Name | Type Name | MethodName | LOC | CC | PC |
---|---|---|---|---|---|---|
proj6 | (default package) | Elevator | Elevator | 8 | 1 | 5 |
proj6 | (default package) | Elevator | getBuilding | 3 | 1 | 0 |
proj6 | (default package) | Elevator | setBuilding | 3 | 1 | 1 |
proj6 | (default package) | Elevator | getLastInvokeTime | 3 | 1 | 0 |
proj6 | (default package) | Elevator | setLastInvokeTime | 3 | 1 | 1 |
proj6 | (default package) | Elevator | getElevatorState | 3 | 1 | 0 |
proj6 | (default package) | Elevator | setElevatorState | 3 | 1 | 1 |
proj6 | (default package) | Elevator | getRequestQueue | 3 | 1 | 0 |
proj6 | (default package) | Elevator | getCapacity | 3 | 1 | 0 |
proj6 | (default package) | Elevator | getCarrying | 3 | 1 | 0 |
proj6 | (default package) | Elevator | setCarrying | 3 | 1 | 1 |
proj6 | (default package) | Elevator | getFloor | 3 | 1 | 0 |
proj6 | (default package) | Elevator | setFloor | 3 | 1 | 1 |
proj6 | (default package) | Elevator | openDoor | 4 | 1 | 0 |
proj6 | (default package) | Elevator | beginCloseDoor | 4 | 1 | 0 |
proj6 | (default package) | Elevator | endCloseDoor | 5 | 1 | 0 |
proj6 | (default package) | Elevator | invitePerson | 5 | 1 | 1 |
proj6 | (default package) | Elevator | moveUp | 4 | 1 | 0 |
proj6 | (default package) | Elevator | moveDown | 4 | 1 | 0 |
proj6 | (default package) | Elevator | moveClockwise | 4 | 1 | 0 |
proj6 | (default package) | Elevator | moveCounterClockwise | 4 | 1 | 0 |
proj6 | (default package) | Elevator | dropPerson | 4 | 1 | 1 |
proj6 | (default package) | Elevator | upFloor | 4 | 1 | 0 |
proj6 | (default package) | Elevator | downFloor | 4 | 1 | 0 |
proj6 | (default package) | Elevator | clockwiseBuilding | 9 | 2 | 0 |
proj6 | (default package) | Elevator | counterClockwiseBuilding | 9 | 2 | 0 |
proj6 | (default package) | Elevator | arrive | 5 | 1 | 0 |
proj6 | (default package) | ElevatorSlot | ElevatorSlot | 4 | 1 | 2 |
proj6 | (default package) | ElevatorSlot | equals | 10 | 3 | 1 |
proj6 | (default package) | ElevatorSlot | hashCode | 3 | 1 | 0 |
proj6 | (default package) | HorizontalElevator | HorizontalElevator | 3 | 1 | 4 |
proj6 | (default package) | HorizontalElevator | run | 39 | 13 | 0 |
proj6 | (default package) | HorizontalElevator | handleMoving | 22 | 4 | 0 |
proj6 | (default package) | HorizontalElevator | handleClosed | 9 | 3 | 2 |
proj6 | (default package) | HorizontalElevator | handleOpening | 27 | 6 | 1 |
proj6 | (default package) | HorizontalElevator | handleClosing | 25 | 6 | 1 |
proj6 | (default package) | InputHandler | InputHandler | 4 | 1 | 2 |
proj6 | (default package) | InputHandler | run | 23 | 5 | 0 |
proj6 | (default package) | LookElevator | LookElevator | 3 | 1 | 4 |
proj6 | (default package) | LookElevator | run | 48 | 15 | 0 |
proj6 | (default package) | LookElevator | handleMoving | 22 | 4 | 0 |
proj6 | (default package) | LookElevator | handleClosed | 27 | 8 | 2 |
proj6 | (default package) | LookElevator | handleOpening | 27 | 6 | 1 |
proj6 | (default package) | LookElevator | handleClosing | 25 | 6 | 1 |
proj6 | (default package) | Main | main | 20 | 2 | 1 |
proj6 | (default package) | MyElevatorRequest | MyElevatorRequest | 13 | 2 | 1 |
proj6 | (default package) | MyElevatorRequest | getType | 3 | 1 | 0 |
proj6 | (default package) | MyElevatorRequest | getId | 3 | 1 | 0 |
proj6 | (default package) | MyElevatorRequest | getBuilding | 3 | 1 | 0 |
proj6 | (default package) | MyElevatorRequest | getFloor | 3 | 1 | 0 |
proj6 | (default package) | MyRequest | MyRequest | 7 | 1 | 5 |
proj6 | (default package) | MyRequest | MyRequest | 7 | 1 | 1 |
proj6 | (default package) | MyRequest | getFromBuilding | 3 | 1 | 0 |
proj6 | (default package) | MyRequest | getToBuilding | 3 | 1 | 0 |
proj6 | (default package) | MyRequest | getFromFloor | 3 | 1 | 0 |
proj6 | (default package) | MyRequest | getToFloor | 3 | 1 | 0 |
proj6 | (default package) | MyRequest | getId | 3 | 1 | 0 |
proj6 | (default package) | OutputWrapper | println | 3 | 1 | 1 |
proj6 | (default package) | RequestQueue | RequestQueue | 4 | 1 | 0 |
proj6 | (default package) | RequestQueue | getRequest | 3 | 1 | 0 |
proj6 | (default package) | RequestQueue | watchForRequest | 8 | 1 | 0 |
proj6 | (default package) | RequestQueue | size | 3 | 1 | 0 |
proj6 | (default package) | RequestQueue | addRequest | 4 | 1 | 1 |
proj6 | (default package) | RequestQueue | removeRequest | 4 | 1 | 1 |
proj6 | (default package) | RequestQueue | isEmpty | 3 | 1 | 0 |
proj6 | (default package) | RequestQueue | isFinish | 3 | 1 | 0 |
proj6 | (default package) | RequestQueue | setNoMoreInput | 4 | 1 | 1 |
proj6 | (default package) | Scheduler | Scheduler | 4 | 1 | 2 |
proj6 | (default package) | Scheduler | dispatchRequest | 20 | 4 | 1 |
proj6 | (default package) | Scheduler | executeAddRequest | 18 | 2 | 1 |
proj6 | (default package) | Scheduler | setFinished | 3 | 1 | 0 |
proj6 | (default package) | Scheduler | joinAll | 11 | 1 | 0 |
Bug
强测和互测没有发现Bug(可能因为相对第一次作业变化不大)。
第三次作业
概述
第三次作业新增了斜向请求和自定义参数,自定义参数其实在前几次作业已经支持了,只需要对输入处理稍作修改就可以实现。主要需要修改的部分在于:
- 控制器
- Request分解
因为这次作业涉及到把请求拆分成几段,于是采用流水线模式,新增类RequestPack
描述为实现一个输入的请求而要顺序完成的一系列请求。InputHandler
会先把输入Request
拆成数个Request
然后打包成RequestPack
。为了管理流水线,控制器线程成为了必需。控制器线程负责以下工作:当一个RequestPack
被某个Elevator
部分完成后会发送到控制器,控制器则将这个RequestPack
又发给下一阶段。
因为一个请求现在有多个阶段,所以程序终止条件不能用以前的,解决方法是增加一个计数器,计数当前未处理完毕(可能有多个阶段)的请求,终止条件是输入结束且没有未处理完毕的请求。
同时,因为结束时电梯线程可能在wait
状态(电梯对应队列为空,等待),所以需要调用interrupt
来使电梯线程离开wait
并判断是不是结束了,并结束电梯线程。
UML
类图
协作图
度量值
Project Name | Package Name | Type Name | MethodName | LOC | CC | PC |
---|---|---|---|---|---|---|
proj7 | (default package) | Controller | Controller | 3 | 1 | 1 |
proj7 | (default package) | Controller | run | 37 | 8 | 0 |
proj7 | (default package) | Controller | getRequestQueuesOfHorizontal | 16 | 2 | 1 |
proj7 | (default package) | Controller | getRequestQueuesOfVertical | 7 | 1 | 1 |
proj7 | (default package) | Elevator | Elevator | 10 | 1 | 7 |
proj7 | (default package) | Elevator | getSpeed | 3 | 1 | 0 |
proj7 | (default package) | Elevator | getScheduler | 3 | 1 | 0 |
proj7 | (default package) | Elevator | getBuilding | 3 | 1 | 0 |
proj7 | (default package) | Elevator | setBuilding | 3 | 1 | 1 |
proj7 | (default package) | Elevator | getLastInvokeTime | 3 | 1 | 0 |
proj7 | (default package) | Elevator | setLastInvokeTime | 3 | 1 | 1 |
proj7 | (default package) | Elevator | getElevatorState | 3 | 1 | 0 |
proj7 | (default package) | Elevator | setElevatorState | 3 | 1 | 1 |
proj7 | (default package) | Elevator | getRequestQueue | 3 | 1 | 0 |
proj7 | (default package) | Elevator | getCapacity | 3 | 1 | 0 |
proj7 | (default package) | Elevator | getCarrying | 3 | 1 | 0 |
proj7 | (default package) | Elevator | setCarrying | 3 | 1 | 1 |
proj7 | (default package) | Elevator | getFloor | 3 | 1 | 0 |
proj7 | (default package) | Elevator | setFloor | 3 | 1 | 1 |
proj7 | (default package) | Elevator | openDoor | 4 | 1 | 0 |
proj7 | (default package) | Elevator | beginCloseDoor | 4 | 1 | 0 |
proj7 | (default package) | Elevator | endCloseDoor | 5 | 1 | 0 |
proj7 | (default package) | Elevator | invitePerson | 5 | 1 | 1 |
proj7 | (default package) | Elevator | moveUp | 4 | 1 | 0 |
proj7 | (default package) | Elevator | moveDown | 4 | 1 | 0 |
proj7 | (default package) | Elevator | moveClockwise | 4 | 1 | 0 |
proj7 | (default package) | Elevator | moveCounterClockwise | 4 | 1 | 0 |
proj7 | (default package) | Elevator | dropPerson | 11 | 2 | 1 |
proj7 | (default package) | Elevator | upFloor | 4 | 1 | 0 |
proj7 | (default package) | Elevator | downFloor | 4 | 1 | 0 |
proj7 | (default package) | Elevator | clockwiseBuilding | 9 | 2 | 0 |
proj7 | (default package) | Elevator | counterClockwiseBuilding | 9 | 2 | 0 |
proj7 | (default package) | Elevator | arrive | 5 | 1 | 0 |
proj7 | (default package) | ElevatorSlot | ElevatorSlot | 4 | 1 | 2 |
proj7 | (default package) | ElevatorSlot | equals | 10 | 3 | 1 |
proj7 | (default package) | ElevatorSlot | hashCode | 3 | 1 | 0 |
proj7 | (default package) | HorizontalElevator | HorizontalElevator | 4 | 1 | 7 |
proj7 | (default package) | HorizontalElevator | canReach | 3 | 1 | 2 |
proj7 | (default package) | HorizontalElevator | run | 33 | 10 | 0 |
proj7 | (default package) | HorizontalElevator | handleMoving | 22 | 4 | 0 |
proj7 | (default package) | HorizontalElevator | handleClosed | 12 | 4 | 2 |
proj7 | (default package) | HorizontalElevator | handleOpening | 27 | 6 | 1 |
proj7 | (default package) | HorizontalElevator | handleClosing | 25 | 6 | 1 |
proj7 | (default package) | InputHandler | InputHandler | 4 | 1 | 2 |
proj7 | (default package) | InputHandler | run | 31 | 6 | 0 |
proj7 | (default package) | LookElevator | LookElevator | 3 | 1 | 6 |
proj7 | (default package) | LookElevator | run | 40 | 11 | 0 |
proj7 | (default package) | LookElevator | handleMoving | 22 | 4 | 0 |
proj7 | (default package) | LookElevator | handleClosed | 27 | 8 | 2 |
proj7 | (default package) | LookElevator | handleOpening | 27 | 6 | 1 |
proj7 | (default package) | LookElevator | handleClosing | 25 | 6 | 1 |
proj7 | (default package) | Main | main | 11 | 1 | 1 |
proj7 | (default package) | MyElevatorRequest | MyElevatorRequest | 17 | 2 | 1 |
proj7 | (default package) | MyElevatorRequest | MyElevatorRequest | 9 | 1 | 7 |
proj7 | (default package) | MyElevatorRequest | getCapacity | 3 | 1 | 0 |
proj7 | (default package) | MyElevatorRequest | getSpeed | 3 | 1 | 0 |
proj7 | (default package) | MyElevatorRequest | getSwitchInfo | 3 | 1 | 0 |
proj7 | (default package) | MyElevatorRequest | getType | 3 | 1 | 0 |
proj7 | (default package) | MyElevatorRequest | getId | 3 | 1 | 0 |
proj7 | (default package) | MyElevatorRequest | getBuilding | 3 | 1 | 0 |
proj7 | (default package) | MyElevatorRequest | getFloor | 3 | 1 | 0 |
proj7 | (default package) | MyRequest | MyRequest | 7 | 1 | 5 |
proj7 | (default package) | MyRequest | MyRequest | 7 | 1 | 1 |
proj7 | (default package) | MyRequest | getFromBuilding | 3 | 1 | 0 |
proj7 | (default package) | MyRequest | getToBuilding | 3 | 1 | 0 |
proj7 | (default package) | MyRequest | getFromFloor | 3 | 1 | 0 |
proj7 | (default package) | MyRequest | getToFloor | 3 | 1 | 0 |
proj7 | (default package) | MyRequest | getId | 3 | 1 | 0 |
proj7 | (default package) | OutputWrapper | println | 3 | 1 | 1 |
proj7 | (default package) | PersonRequestPack | PersonRequestPack | 3 | 1 | 1 |
proj7 | (default package) | PersonRequestPack | getProcessingRequest | 3 | 1 | 0 |
proj7 | (default package) | PersonRequestPack | submitFinishedRequest | 3 | 1 | 0 |
proj7 | (default package) | PersonRequestPack | isFinish | 3 | 1 | 0 |
proj7 | (default package) | RequestQueue | RequestQueue | 3 | 1 | 0 |
proj7 | (default package) | RequestQueue | getRequest | 3 | 1 | 0 |
proj7 | (default package) | RequestQueue | watchForRequest | 8 | 1 | 0 |
proj7 | (default package) | RequestQueue | size | 3 | 1 | 0 |
proj7 | (default package) | RequestQueue | addRequest | 4 | 1 | 1 |
proj7 | (default package) | RequestQueue | removeRequest | 4 | 1 | 1 |
proj7 | (default package) | RequestQueue | isEmpty | 3 | 1 | 0 |
proj7 | (default package) | Scheduler | Scheduler | 6 | 1 | 2 |
proj7 | (default package) | Scheduler | inc | 4 | 1 | 0 |
proj7 | (default package) | Scheduler | dec | 4 | 1 | 0 |
proj7 | (default package) | Scheduler | isNoActiveRequest | 3 | 1 | 0 |
proj7 | (default package) | Scheduler | getElevatorMap | 3 | 1 | 0 |
proj7 | (default package) | Scheduler | getRequestPacks | 3 | 1 | 0 |
proj7 | (default package) | Scheduler | startController | 3 | 1 | 0 |
proj7 | (default package) | Scheduler | sendBackRequestPack | 4 | 1 | 1 |
proj7 | (default package) | Scheduler | splitRequest | 27 | 5 | 1 |
proj7 | (default package) | Scheduler | executeAddRequest | 19 | 2 | 1 |
proj7 | (default package) | Scheduler | setFinished | 4 | 1 | 0 |
proj7 | (default package) | Scheduler | isFinished | 3 | 1 | 0 |
proj7 | (default package) | Scheduler | joinAll | 18 | 1 | 0 |
Bug
加了一个错误的判断:”如果请求仅在同一楼层移动,则不进行请求切割”,很明显该楼层可能根本没有横向电梯。
但是强测居然过了。
然后互测被Hack了8/9。
思考
同步块与锁
在三次作业中只使用了基本的synchronized
同步块,上锁的原则是该对象可变,且有不止一个线程会修改它。例如请求队列电梯和调度器都会读写,读写时就需要上锁。
锁的粒度不能太大,否则会严重影响性能,我第一次作业前期因为所有电梯共用一个请求队列导致CPU超时,(一个电梯要拿请求其他电梯都得等)。因为A座的电梯不可能运B座的人,A座电梯拿请求时把B座锁了是没有道理的。之后改成每个电梯一个队列修复了这个问题。(也许读写锁也可以解决这个问题?因为只有调度器写,电梯线程读,且可以同时读不加锁提高效率)
wait
,notify
而不是忙等,感觉这是基础的要求,(忙等好像必然CTLE)。
如何判断处理结束(输入结束且队列为空?)并通知每个线程处理结束,且保证每个线程能够收到通知并退出(可能在wait
不会收到通知,需要interrupt
)也是需要考虑的问题。
调度器
第一次第二次作业相当于没有单独的调度器线程,输入线程直接把任务交给各个电梯,而电梯自己负责自己的移动策略(如LookElevator
使用Look策略处理自己队列的请求)。第三次作业新增的调度线程其实只处理流水线模式下的需要把部分处理的请求再次下发的问题。
在调度策略上,调度器不关心电梯的状态(在几楼,人满没满),只通过一些可以在不影响性能的条件下获取的信息,(请求队列长短,对于第三次作业总移动距离)决定把请求分配给哪个电梯,即在请求到来时立即分配,而不是根据动态信息分配。唯一的动态分配是在同一座或同一层内选择当前等待队列较短的电梯加入请求。(例如到来一个请求,它被拆成哪三段在此时已经确定了(基准策略),在流水线控制器将各段进行分配时(完成一段被电梯线程发送回来再分配下一段),比如该请求先要从A座5-10层,A座有两部电梯,流水线控制器会把请求发给等待队列短的电梯,而不考虑电梯具体在几层)。
具体在协作图表述的可能清晰一些。
调度器与电梯线程的交互如上所述,使用请求队列,因为调度器不会去读电梯线程的状态(通过动态信息调度),所以交互相对较为简单。感觉这样虽然可能性能不是特别优秀,但是简单的设计可以减少错误的发生,算是一种取舍吧。
可扩展性
Elevator
抽象类其实在理论上不限制电梯以什么方向运动(可以先横着走,再竖着走),提供了足够的扩展性,改变策略或移动方向只需要继承这个类。
在二三次迭代开发时,没有进行大规模重构,说明可扩展性尚可?
Hack策略
感觉如果没有自己实现数据生成器和评测机,对于第二单元根本没有办法hack。相比第一单元,第二单元的输出几乎无法手动解读,输入也难以手工构造。
所以,摸了,就没怎么hack。
通过第三次作业别人hack我的数据我发现他大概是随机数据黑盒测试然后发现一个点中了,然后缩小范围(删去一些非关键数据行)又帮我测了一次,虽然让我的数据不太好看但是对我发现Bug还是很有帮助。
多线程寻找bug显然比单线程困难很多,对于构造hack数据,我认为还是应该从边界条件入手,比如在同一座加入数个电梯,然后在这一座加请求,如果线程安全有问题就很容易出错,或者在同一座加很多请求,在一座加入电梯后(在允许范围内)马上对其发请求。
如果真要从阅读代码入手,其实多线程条件下,调试器也是可以使用的,不过需要看清楚是哪一个线程停在断点(会显示),然后分析此时的行为。(感觉还是随机数据生成效率高)。
心得与体会
虽然本单元的重点是多线程,但是其实我三次作业在强测互测发现的Bug都和多线程没有太大关系。虽说如此,在完成作业的过程中还是遇到了不少线程相关的问题,比如线程wait
无法退出,或者刚开始对Java线程同步完全不熟悉的时候,在没有持有锁的时候调用wait
(不在synchronized
块里调wait
为什么是个运行时错误。。),在第三次作业时因为新增了单独的调度器线程,如何在保证请求处理完毕的情况下正确退出也困扰了我很久。
在层次化设计上,做到了请求分配和电梯响应的解耦,电梯只负责自己的运输,调度器只负责把请求拆解并发送到相应的电梯。同时通过继承抽象类Elevator
实现了电梯类的可扩展。通过合理的设计,使得三次作业的迭代基本只是增加功能,而不是推倒重来。
通过这一单元的学习还是收获颇丰,初次实践了并发编程,进一步加深了对于一些设计模式的理解。
其他语言的线程同步
本单元学习并实践了并发编程,于是了解了一些其他语言的线程同步方法。
One increasingly popular approach to ensuring safe concurrency is message passing, where threads or actors communicate by sending each other messages containing data. Here’s the idea in a slogan from the Go language documentation: “Do not communicate by sharing memory; instead, share memory by communicating.”
Golang
推荐使用channel
基于消息传递进行线程间通信,以保证安全的并发。通过本单元的学习和实践,大概也能感受到共享内存确实也是Bug的一大来源。对应到我们的电梯,如果把乘客信息通过以内存共享的形式,调度器和电梯都对这一区域读写,感觉复杂度不仅很高,还容易出现线程安全问题,相反如果使用消息传递风格,每个电梯维护一个请求队列,调度器把request
发给各个电梯,可能比较容易维护。
In a way, channels in any programming language are similar to single ownership, because once you transfer a value down a channel, you should no longer use that value. Shared memory concurrency is like multiple ownership: multiple threads can access the same memory location at the same time. As you saw in Chapter 15, where smart pointers made multiple ownership possible, multiple ownership can add complexity because these different owners need managing. Rust’s type system and ownership rules greatly assist in getting this management correct. For an example, let’s look at mutexes, one of the more common concurrency primitives for shared memory.
上面这段文字进一步解释了共享内存因为需要管理多个对资源具有所有权的线程,增加了复杂度。同时所有权检查作为Rust
独有的特性,所有权检查也解决了一些并发问题,即把一些运行时错误提前到编译期发现,在本单元被各种无法复现/时隐时现的运行时错误折磨后,通过编译期检查获得的额外程序安全性也是很有吸引力的。
其实虽然Java可能没有可以发现很多并发错误的编译器检查,我们可以通过注意编码时的规范来做到这一点。比如一个错误:
// in scheduler class
HashMap<Floor, ArrayList<Request>> requestMap;
public synchronized ArrayList<Request> get_request(Floor floor){
return requestMap.get(floor);
}
把其中一个数组给引用出去了,这个数组相当于就可以绕过锁随意读写了。