需求简述:
模拟一个多线程实时电梯系统,各楼座有且仅有一台纵向电梯,处理已知起终点的同楼座乘客请求。
代码构架:
|- Unit2:主类 |- InputHandler:输入线程 |- Controller:各楼座候乘表 |- Elevator:电梯线程 |- OutputHandler:输出处理
采用生产者-消费者设计模式,托盘为各楼座候乘表,即实际上有5张候乘表。
同一楼座的电梯共享该楼座的候乘表,读写信息均在候乘表中加锁实现,避免几个线程之间直接交互以及数据不同步的问题。
对 TimableOutput 加锁封装为输出处理类,避免出现以下情况导致输出时间戳非递增:
1获取时间戳 --> 2获取时间戳 --> 2输出 --> 1输出
规模分析:
共221个代码行
复杂度分析:
method | ev(G) | iv(G) | v(G) |
---|---|---|---|
Controller.addRequest(PersonRequest) | 1.0 | 1.0 | 1.0 |
Controller.getRequests() | 1.0 | 1.0 | 1.0 |
Controller.isInputAlive() | 1.0 | 1.0 | 1.0 |
Controller.setInputAlive(boolean) | 1.0 | 1.0 | 1.0 |
Controller.setRequests(ArrayList) | 1.0 | 1.0 | 1.0 |
Elevator.Elevator(char, int, Controller) | 1.0 | 1.0 | 1.0 |
Elevator.move(long) | 1.0 | 1.0 | 2.0 |
Elevator.openAndClose() | 1.0 | 13.0 | 15.0 |
InputHandler.InputHandler(ElevatorInput, HashMap) | 1.0 | 1.0 | 1.0 |
OutputHandler.initStartTimestamp() | 1.0 | 1.0 | 1.0 |
OutputHandler.println(String) | 1.0 | 1.0 | 1.0 |
Unit2.main(String[]) | 1.0 | 3.0 | 3.0 |
InputHandler.run() | 3.0 | 6.0 | 6.0 |
Elevator.run() | 9.0 | 22.0 | 25.0 |
Total | 24.0 | 54.0 | 60.0 |
Average | 1.71 | 3.86 | 4.29 |
需求简述:
模拟一个多线程实时电梯系统,初始各楼座有且仅有一台纵向电梯;可动态增加横向、纵向电梯;处理已知起终点的同楼座或同楼层乘客请求。
代码构架:
|- Unit2:主类 |- InputHandler:输入线程 |- GlobalController:总候乘表 |- Controller:各楼座/层候乘表 |- Elevator:电梯线程 |- ElevatorP:横向电梯线程 |- ElevatorV:纵向电梯线程 |- OutputHandler:输出处理
仅为完成本次作业的代码与第一次作业基本相同,只是将电梯线程抽象出来——把方向计算、距离计算抽象成子函数,并在横向、纵向电梯类内具体实现这些操作。此时,两类电梯的开关门、运行、捎带等策略完全一致,可以将第一次作业的代码完全复用,并且没有出现大段重复代码。
因上述实现较为简单,我在本次作业中尝试提前实现换乘。将原来的一级托盘结构更改为二级托盘结构,分别装载所有请求和具体座/层请求。详见后文“调度器设计”。
规模分析:
共489个代码行
复杂度分析:
method | ev(G) | iv(G) | v(G) |
---|---|---|---|
Controller.addRequest(PersonRequest) | 1.0 | 1.0 | 1.0 |
Controller.getRequests() | 1.0 | 1.0 | 1.0 |
Controller.isInputAlive() | 1.0 | 1.0 | 1.0 |
Controller.setInputAlive(boolean) | 1.0 | 1.0 | 1.0 |
Controller.setRequests(ArrayList) | 1.0 | 1.0 | 1.0 |
Elevator.Elevator(int, GlobalController, Controller) | 1.0 | 1.0 | 1.0 |
Elevator.getBlock() | 1.0 | 1.0 | 1.0 |
Elevator.getDirection() | 1.0 | 1.0 | 1.0 |
Elevator.getEleId() | 1.0 | 1.0 | 1.0 |
Elevator.getFloor() | 1.0 | 1.0 | 1.0 |
Elevator.inDir(PersonRequest) | 1.0 | 1.0 | 1.0 |
Elevator.openAndClose() | 1.0 | 5.0 | 5.0 |
Elevator.setBlock(char) | 1.0 | 1.0 | 1.0 |
Elevator.setCtrlKey(String) | 1.0 | 1.0 | 1.0 |
Elevator.setDirection(int) | 1.0 | 1.0 | 1.0 |
Elevator.setFloor(int) | 1.0 | 1.0 | 1.0 |
Elevator.takeIn(String) | 1.0 | 11.0 | 11.0 |
Elevator.takeOff(String) | 1.0 | 4.0 | 4.0 |
Elevator.waitInDir(PersonRequest) | 1.0 | 1.0 | 1.0 |
ElevatorP.ElevatorP(int, int, GlobalController, Controller) | 1.0 | 1.0 | 1.0 |
ElevatorP.isFrom(PersonRequest) | 1.0 | 1.0 | 1.0 |
ElevatorP.isTo(PersonRequest) | 1.0 | 1.0 | 1.0 |
ElevatorP.move(long) | 1.0 | 4.0 | 4.0 |
ElevatorP.reqDir(PersonRequest) | 1.0 | 1.0 | 3.0 |
ElevatorP.waitDir(PersonRequest) | 1.0 | 1.0 | 3.0 |
ElevatorV.ElevatorV(char, int, GlobalController, Controller) | 1.0 | 1.0 | 1.0 |
ElevatorV.isFrom(PersonRequest) | 1.0 | 1.0 | 1.0 |
ElevatorV.isTo(PersonRequest) | 1.0 | 1.0 | 1.0 |
ElevatorV.reqDir(PersonRequest) | 1.0 | 1.0 | 1.0 |
ElevatorV.waitDir(PersonRequest) | 1.0 | 1.0 | 1.0 |
GlobalController.GlobalController() | 1.0 | 1.0 | 1.0 |
GlobalController.addController(ElevatorRequest) | 1.0 | 2.0 | 2.0 |
GlobalController.addElevator(ElevatorRequest) | 1.0 | 3.0 | 3.0 |
GlobalController.addElevatorP(ElevatorRequest) | 1.0 | 2.0 | 2.0 |
GlobalController.addElevatorV(ElevatorRequest) | 1.0 | 2.0 | 2.0 |
GlobalController.infoInputKilled() | 1.0 | 2.0 | 2.0 |
GlobalController.initControllers() | 1.0 | 2.0 | 2.0 |
GlobalController.initElevators() | 1.0 | 2.0 | 2.0 |
GlobalController.withoutTransfers() | 1.0 | 1.0 | 1.0 |
InputHandler.InputHandler(ElevatorInput) | 1.0 | 1.0 | 1.0 |
OutputHandler.initStartTimestamp() | 1.0 | 1.0 | 1.0 |
OutputHandler.println(String) | 1.0 | 1.0 | 1.0 |
Unit2.main(String[]) | 1.0 | 1.0 | 1.0 |
GlobalController.addNextRequest(String, PersonRequest) | 3.0 | 4.0 | 5.0 |
InputHandler.run() | 3.0 | 6.0 | 6.0 |
ElevatorV.move(long) | 4.0 | 4.0 | 4.0 |
Elevator.ifOpen() | 5.0 | 11.0 | 12.0 |
Elevator.run() | 5.0 | 12.0 | 14.0 |
GlobalController.addRequest(PersonRequest) | 7.0 | 8.0 | 10.0 |
Total | 70.0 | 116.0 | 126.0 |
Average | 1.43 | 2.37 | 2.57 |
需求简述:
模拟一个多线程实时电梯系统,初始各楼座有且仅有一台纵向电梯、1层有且仅有一台可达所有楼座的横向电梯;可动态增加横向、纵向电梯,新电梯可自定义限载人数、运行速度、(横向电梯)可达楼座;处理已知起终点的任意乘客请求。
代码构架
|- Unit2:主类 |- InputHandler:输入线程 |- GlobalController:总候乘表 |- Controller:各楼座/层候乘表 |- Elevator:电梯线程 |- ElevatorP:横向电梯线程 |- ElevatorV:纵向电梯线程 |- OutputHandler:输出处理
规模分析:
共557个代码行
复杂度分析:
method | ev(G) | iv(G) | v(G) |
---|---|---|---|
Controller.addElevator(ElevatorRequest) | 1.0 | 1.0 | 1.0 |
Controller.addRequest(PersonRequest) | 1.0 | 1.0 | 1.0 |
Controller.getElevators() | 1.0 | 1.0 | 1.0 |
Controller.getRequests() | 1.0 | 1.0 | 1.0 |
Controller.isInputAlive() | 1.0 | 1.0 | 1.0 |
Controller.setInputAlive(boolean) | 1.0 | 1.0 | 1.0 |
Controller.setRequests(ArrayList) | 1.0 | 1.0 | 1.0 |
Elevator.Elevator(int, int, long, GlobalController, Controller) | 1.0 | 1.0 | 1.0 |
Elevator.getBlock() | 1.0 | 1.0 | 1.0 |
Elevator.getCtrl() | 1.0 | 1.0 | 1.0 |
Elevator.getDirection() | 1.0 | 1.0 | 1.0 |
Elevator.getEleId() | 1.0 | 1.0 | 1.0 |
Elevator.getFloor() | 1.0 | 1.0 | 1.0 |
Elevator.inDir(PersonRequest) | 1.0 | 1.0 | 1.0 |
Elevator.move(long) | 1.0 | 1.0 | 1.0 |
Elevator.openAndClose() | 1.0 | 5.0 | 5.0 |
Elevator.setBlock(char) | 1.0 | 1.0 | 1.0 |
Elevator.setCtrlKey(String) | 1.0 | 1.0 | 1.0 |
Elevator.setDirection(int) | 1.0 | 1.0 | 1.0 |
Elevator.setFloor(int) | 1.0 | 1.0 | 1.0 |
Elevator.specialWait() | 1.0 | 1.0 | 1.0 |
Elevator.takeIn(String) | 1.0 | 13.0 | 13.0 |
Elevator.takeOff(String) | 1.0 | 4.0 | 4.0 |
Elevator.waitInDir(PersonRequest) | 1.0 | 1.0 | 1.0 |
ElevatorP.ElevatorP(int, int, int, long, int, GlobalController, Controller) | 1.0 | 1.0 | 1.0 |
ElevatorP.canCarry(PersonRequest) | 1.0 | 1.0 | 1.0 |
ElevatorP.canStop() | 1.0 | 1.0 | 1.0 |
ElevatorP.isFrom(PersonRequest) | 1.0 | 1.0 | 1.0 |
ElevatorP.isTo(PersonRequest) | 1.0 | 1.0 | 1.0 |
ElevatorP.move(long) | 1.0 | 4.0 | 4.0 |
ElevatorP.reqDir(PersonRequest) | 1.0 | 1.0 | 3.0 |
ElevatorP.waitDir(PersonRequest) | 1.0 | 1.0 | 3.0 |
ElevatorV.ElevatorV(char, int, int, long, GlobalController, Controller) | 1.0 | 1.0 | 1.0 |
ElevatorV.canCarry(PersonRequest) | 1.0 | 1.0 | 1.0 |
ElevatorV.canStop() | 1.0 | 1.0 | 1.0 |
ElevatorV.isFrom(PersonRequest) | 1.0 | 1.0 | 1.0 |
ElevatorV.isTo(PersonRequest) | 1.0 | 1.0 | 1.0 |
ElevatorV.reqDir(PersonRequest) | 1.0 | 1.0 | 1.0 |
ElevatorV.waitDir(PersonRequest) | 1.0 | 1.0 | 1.0 |
GlobalController.GlobalController() | 1.0 | 1.0 | 1.0 |
GlobalController.addController(ElevatorRequest) | 1.0 | 2.0 | 2.0 |
GlobalController.addElevator(ElevatorRequest) | 1.0 | 3.0 | 3.0 |
GlobalController.addElevatorP(ElevatorRequest) | 1.0 | 2.0 | 2.0 |
GlobalController.addElevatorV(ElevatorRequest) | 1.0 | 2.0 | 2.0 |
GlobalController.addRequest(PersonRequest) | 1.0 | 5.0 | 5.0 |
GlobalController.infoInputKilled() | 1.0 | 2.0 | 2.0 |
GlobalController.initControllers() | 1.0 | 2.0 | 2.0 |
GlobalController.initElevators() | 1.0 | 2.0 | 2.0 |
GlobalController.withoutTransfers() | 1.0 | 1.0 | 1.0 |
InputHandler.InputHandler(ElevatorInput) | 1.0 | 1.0 | 1.0 |
OutputHandler.initStartTimestamp() | 1.0 | 1.0 | 1.0 |
OutputHandler.println(String) | 1.0 | 1.0 | 1.0 |
Unit2.main(String[]) | 1.0 | 1.0 | 1.0 |
ElevatorP.specialWait() | 3.0 | 2.0 | 3.0 |
GlobalController.addNextRequest(String, PersonRequest) | 3.0 | 4.0 | 5.0 |
InputHandler.run() | 3.0 | 6.0 | 6.0 |
ElevatorV.move(long) | 4.0 | 4.0 | 4.0 |
Elevator.run() | 5.0 | 13.0 | 15.0 |
GlobalController.findTranFloor(char, char, int, int) | 5.0 | 3.0 | 6.0 |
Elevator.ifOpen() | 6.0 | 12.0 | 14.0 |
Total | 82.0 | 131.0 | 144.0 |
Average | 1.37 | 2.18 | 2.40 |
本单元代码中的所有用到共享对象的部分,我都选择了使用 synchronized 加锁。主要用途如下:
共享对象的类方法
如果方法涉及到读写本类内属性的操作,给该方法上锁。
// Controller.java // requests为类内属性 public synchronized void addRequest(PersonRequest pr) { requests.add(pr); this.notifyAll(); }
线程对象中的同步块
如果线程对象中涉及到连续读写共享对象,且上下文连接紧密,不可中断的操作,给该代码块上锁。
设置同步块的大多数场景为:电梯对象遍历访问候乘表的所有请求。此时须保证遍历的全过程中候乘表不能被更改,否则将会出现线程不安全错误。
// ElevatorP.java // getCtrl()返回共享对象 synchronized (getCtrl()) { for (PersonRequest x: getCtrl().getRequests()) { char from = x.getFromBuilding(); char to = x.getToBuilding(); if (((switchInfo >> (from - 'A')) & 1) + ((switchInfo >> (to - 'A')) & 1) == 2) { return false; } } }
在第一次作业中,我的共享对象全部是同一个类的实例,且共享对象之间不会有互相访问的情况。因此,用最粗暴的方法“只要涉及共享对象,全部上锁!”不会导致死锁。
从第二次作业开始,我将原来的生产-消费模式中的一级托盘重构为二级托盘。因此在实现时特别留意,防止两类共享对象肆意加锁导致的死锁。具体策略如下:
// GlobalController.java // 一级托盘:GlobalController,二级托盘:Controller public synchronized void infoInputKilled() { this.inputAlive = false; for (Controller ctrl : controllers.values()) { synchronized (ctrl) { ctrl.setInputAlive(false); ctrl.notifyAll(); } } }
// Elevator.java // gctrl为一级托盘对象,ctrl为二级托盘对象 boolean transEnd = gctrl.withoutTransfers(); // 在二级托盘同步块外调用一级托盘 synchronized (ctrl) { if ((ctrl.getRequests().isEmpty() || specialWait()) && direction == 0) { if (ctrl.isInputAlive() || !transEnd) { // * ... } else { return; } } else { ... } }
由于我的同步块内基本上都是读写并举,改用读写锁的轻量化意义不大,所以全程只用了 synchronized 的方式上锁。