OO2022第二单元总结

OO2022第二单元总结

本单元内容是电调调度与多线程,因为之前完全没有真正上手写过多线程程序,所以相比第一单元对我而言相对熟悉的内容,完成第二单元需要更多的思考。

在代码的架构设计上,使用了最基本的生产者-消费者模式,简而言之就是一个线程解析请求,然后分发到各个电梯的请求队列。

第一二次作业请求解析与分发都和输入处理在一个线程,严格来讲没有控制器,第三次作业由于采用了流水线模式,部分完成的请求需要再次分发以完成下一阶段,所以增加了控制器线程。

在调度策略上,采用的是能想到的最简单的方法,即纵向电梯采用Look算法,横向电梯只朝一个方向运动,第三次作业拆分使用基准策略的三段拆分。

第一次作业

概述

这一次作业相对较为简单,困难可能在于对多线程还不太熟悉。

最开始让所有电梯共用一个请求队列,导致频繁的上锁操作(有一个请求来了,所有电梯都被notify,都去看一眼),导致CPU超时,于是改成每个电梯一个队列。(也有可能是Java8的stream在小规模数据下效率相对循环偏低?但是之后的作业里仍然使用大量stream,好像也没出现问题)

UML

类图

注:UML图中的公开字段代表有getter和setter的字段

注2:UML图中仅包含关键字段和方法(感觉一堆getter,setter放在里面意义不大,不能让人得出任何额外信息,而且画太多了mermaid会出分辨率问题)

classDiagram class Thread{ (many methods by java library) } class Elevator{ << abstract >> +lastInvokeTime: long +carrying: ArrayList~Request~ +capacity: int +requestQueue: RequestQueue +building: int +elevatorState: ElevatorState +floor: int +openDoor() void +beginCloseDoor() void +moveDown() void +invitePerson(Request) void +dropPerson(Request) void +moveUp() void +downFloor() void +endCloseDoor() void +upFloor() void } class LookElevator{ +run() void } class Building{ <<enumeration>> A B C D E } class ElevatorState{ <<enumeration>> Opened MovingUp MovingDown Opening Closing Closed } class LookDirection{ <<enumeration>> Up Down } class RequestQueue{ -noMoreInput: boolean +watchForRequest() void +addRequest(Request) void +removeRequest(Request) void +isFinish() boolean +isEmpty() boolean +setNoMoreInput(boolean) void +getRequest() ArrayList~Request~ } class Scheduler{ +setFinished() void +dispatchRequest(Request) void } class OutputWrapper{ println(String) void } class InputHandler{ run() void } class Request{ +fromBuilding: int +toBuilding: int +fromFloor: int +toFloor: int +id: int } LookElevator --|> Elevator Building --* Elevator ElevatorState --* Elevator LookDirection --* LookElevator RequestQueue --* Elevator Scheduler --> RequestQueue Elevator --|> Thread InputHandler --|> Thread InputHandler --> Scheduler Elevator --> OutputWrapper

协作图

sequenceDiagram participant Main participant InputHandler participant Scheduler participant Elevator activate Main Main->>InputHandler: start Main->>Elevator: start deactivate Main par dispatch requests loop input not finished(^D) InputHandler->>+Scheduler: dispatch Request Scheduler->>RequestQueue: add Request end and respong requests loop input not finished or request queue not empty Elevator->>+RequestQueue: get Request RequestQueue-->>-Elevator: return Elevator->>Elevator: run end end InputHandler->>Scheduler: set finished Scheduler->>RequestQueue: set finished

如上文所说,使用了最基本的生产者-消费者模式,一个线程解析请求,然后分发到各个电梯的请求队列。

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

类图

classDiagram class Thread{ (many methods by java library) } class Elevator{ << abstract >> +lastInvokeTime: long +carrying: ArrayList~Request~ +capacity: int +requestQueue: RequestQueue +building: int +elevatorState: ElevatorState +floor: int +openDoor() void +beginCloseDoor() void +moveDown() void +invitePerson(Request) void +dropPerson(Request) void +moveUp() void +downFloor() void +moveClockwise() void +moveCounterClockwise() void +clockwiseBuilding() void +counterClockwiseBuilding() void +endCloseDoor() void +upFloor() void } class LookElevator{ +run() void } class HorizontalElevator{ +run() void } class Building{ <<enumeration>> A B C D E } class ElevatorState{ <<enumeration>> Opened MovingUp MovingDown Opening Closing Closed } class LookDirection{ <<enumeration>> Up Down } class RequestQueue{ -noMoreInput: boolean +watchForRequest() void +addRequest(Request) void +removeRequest(Request) void +isFinish() boolean +isEmpty() boolean +setNoMoreInput(boolean) void +getRequest() ArrayList~Request~ } class Scheduler{ +setFinished() void +dispatchRequest(Request) void +executeAddRequest(MyElevatorRequest) void } class OutputWrapper{ +println(String) void } class InputHandler{ +run() void } class ElevatorSlot{ -type: ElevatorType -floorOrBuilding: int } class Request{ +fromBuilding: int +toBuilding: int +fromFloor: int +toFloor: int +id: int } class MyElevatorRequest{ +type: ElevatorType +id: int +building: int +floor: int } LookElevator --|> Elevator HorizontalElevator --|> Elevator Building --* Elevator ElevatorState --* Elevator LookDirection --* LookElevator RequestQueue --* Elevator Scheduler --> RequestQueue Elevator --|> Thread InputHandler --|> Thread InputHandler --> Scheduler Elevator --> OutputWrapper Scheduler --> ElevatorSlot

因为之前考虑到了可能会在之后加入更多种类的电梯,于是使用抽象类Elevator定义了电梯的公共行为,第二次作业加入水平电梯只需要继承上述抽象类即可。

因为不再是只有五部电梯,所以定义ElevatorSlot描述楼层和楼号信息,使用HashMap<ElevatorSlot,ArrayList<Elevator>>在找符合请求的电梯时就不需要把所有电梯扫描一遍。(优化效果未知)

协作图

sequenceDiagram participant Main participant InputHandler participant Scheduler participant Elevator activate Main Main->>InputHandler: start Main->>Elevator: start deactivate Main par dispatch requests loop input not finished(^D) alt request of what kind? InputHandler->>+Scheduler: dispatch Request Scheduler->>RequestQueue: add Request else InputHandler->>Scheduler: execute add Elevator Scheduler->>RequestQueue: create Scheduler->>Elevator: start end end and respong requests loop input not finished or request queue not empty Elevator->>+RequestQueue: get Request RequestQueue-->>-Elevator: return Elevator->>Elevator: run end end InputHandler->>Scheduler: set finished Scheduler->>RequestQueue: set finished

度量值

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

类图

classDiagram class Thread{ (many methods by java library) } class Elevator{ << abstract >> +lastInvokeTime: long +carrying: ArrayList~MyRequest~ +capacity: int +requestQueue: RequestQueue +building: int +elevatorState: ElevatorState +floor: int +openDoor() void +beginCloseDoor() void +moveDown() void +invitePerson(MyRequest) void +dropPerson(Request) void +moveUp() void +downFloor() void +moveClockwise() void +moveCounterClockwise() void +clockwiseBuilding() void +counterClockwiseBuilding() void +endCloseDoor() void +upFloor() void } class LookElevator{ +run() void } class HorizontalElevator{ +run() void } class Building{ <<enumeration>> A B C D E } class ElevatorState{ <<enumeration>> Opened MovingUp MovingDown Opening Closing Closed } class LookDirection{ <<enumeration>> Up Down } class RequestQueue{ -noMoreInput: boolean +watchForRequest() void +addRequest(MyRequest) void +removeRequest(MyRequest) void +isFinish() boolean +isEmpty() boolean +setNoMoreInput(boolean) void +getRequest() ArrayList~MyRequest~ } class Scheduler{ +elevatorMap: HashMap~ElevatorSlot,ArrayList~Elevator~~ +elevators: Elevator +requestPacks: ArrayDeque~PersonRequestPack~ -controller: Controller -isFinished: boolean -counter: int +setFinished() void +splitRequest(MyRequest) void +executeAddRequest(MyElevatorRequest) void +isFinished() boolean +sendBackRequestPack(PersonRequestPack) void +inc() void +dec() void +isNoActiveRequest() boolean } class OutputWrapper{ +println(String) void } class InputHandler{ +run() void } class ElevatorSlot{ -type: ElevatorType -floorOrBuilding: int } class MyRequest{ +fromBuilding: int +toBuilding: int +fromFloor: int +toFloor: int +id: int } class MyElevatorRequest{ +type: ElevatorType +id: int +building: int +floor: int +capacitry: int +speed: double +switchInfo: int } class PersonRequestPack{ -requests: ArrayList~MyRequest~ +getProcessingRequest() MyRequest +submitFinishedRequest() void +isFinish() boolean } class Controller{ +run() void } LookElevator --|> Elevator HorizontalElevator --|> Elevator Building --* Elevator ElevatorState --* Elevator LookDirection --* LookElevator RequestQueue --* Elevator Scheduler --> RequestQueue Elevator --|> Thread InputHandler --|> Thread InputHandler --> Scheduler Elevator --> OutputWrapper Scheduler --> ElevatorSlot MyRequest --* PersonRequestPack Controller --|> Thread Controller --> Scheduler

协作图

sequenceDiagram participant Main participant InputHandler participant Scheduler participant Elevator activate Main Main->>InputHandler: start Main->>Elevator: start Main->>Scheduler: start Controller Scheduler->>Controller: start deactivate Main par handle input loop input not finished(^D) alt request of what kind? InputHandler->>Scheduler: split Request else InputHandler->>Scheduler: execute add Elevator Scheduler->>RequestQueue: create Scheduler->>Elevator: start end end and manage requests loop request count > 0 or input not finished Controller->>+Scheduler: get Requests need to be dispatched Scheduler-->>-Controller: return Requests Controller->>RequestQueue: add Request end and respong requests loop scheduler state not set to finished Elevator->>+RequestQueue: get Request RequestQueue-->>-Elevator: return Elevator->>Elevator: run alt is this request finished? Elevator->>Scheduler: decrease request counter else not yet Elevator->>Scheduler: send back request end end end InputHandler->>Scheduler: set finished

度量值

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);
}

把其中一个数组给引用出去了,这个数组相当于就可以绕过锁随意读写了。

posted @ 2022-04-28 20:58  aaicy64  阅读(15)  评论(0编辑  收藏  举报