Buaa_OO_二单元总结
文章目录
题目背景
一个多线程实时电梯系统。
系统从标准输入中输入请求信息,程序进行接收和处理,模拟电梯运行,将必要的运行信息通过输出接口进行输出。
hw1
- 实现功能
- 上下行,开关门,以及模拟乘客的进出。
hw2
- 实现功能
- 上下行,开关门,模拟乘客的进出;
- 模拟电梯系统扩建和日常维护时乘客的调度。
hw3
- 实现功能
- 上下行,开关门,模拟乘客的进出;
- 模拟电梯系统扩建和日常维护时乘客的调度;
- 新增的电梯可达性支持自定义;
- 任意楼层设置电梯服务许可限制。
架构设计
辅助类
-
Person
对官方
.jar
包中的PersonRequest
进行封装,在官方包的基础上增加了在Person
中自定义PersonRequest
的fromFloor
和toFloor
的功能。 -
RequestQueue
储存指令的队列。
-
Elevator
储存电梯的基本参数。
-
AllState
储存电梯的实时运行状态。
-
StateElevator
为电梯的运行状态提供一个统一接口。
-
Waiting
- 等待状态1
- 操作: 无。
-
Loading
- 即将运行状态1,介于
Waiting
与Arrive
之间。 - 操作:无。
- 即将运行状态1,介于
-
Arrive
- 抵达状态1
- 操作:执行
ARRIVE
OPEN
IN
OUT
CLOSE
操作。
-
Running
- 运行状态1
- 操作:改变电梯当前楼层。
主线程
-
Main
构造辅助类及依次启动守护线程、运行线程、调度线程与输入线程。
守护线程
-
Daemon
当所有运行线程都结束时,不断对调度线程中等待队列进行
notifyAll
。
输入线程
-
InputThread
读入指令。
调度线程
-
DispatchThread
将读入的指令分配给相应的运行线程。
运行线程
-
OperateThread
依次执行调度线程分配的指令。
迭代分析
基于UML类图的迭代分析
hw1
hw2
-
新增
Person
类,继承PersonRequest
,对官方.jar
包中的PersonRequest
进行转储,在官方包的基础上增加了在Person
中自定义PersonRequest
的fromFloor
和toFloor
的功能。 -
输入线程
InputThread
-
新增对每个读入指令具体类型的判断,并进行相应的操作。
-
若为
PersonRequest
,则构造Person
储存PersonRequest
中的数据; -
若为
ElevatorRequset
,则调用DispatchThread.addElevator()
; -
若为
MaitainRequest
,则调用DispatchThread.maintainElevator()
。
-
-
-
调度线程
DispatchThread
-
调度策略的改变,见调度器设计;
-
新增静态方法
DispatchThread.addElevator()
;DispatchThread.addElevator()
新建电梯类,增加与其对应的运行进程。
-
新增静态方法
DispatchThread.maintainElevator()
;DispatchThread.maintainElevator()
将相应电梯所对应的运行进程的维修信号
signalMaintain
置true
。
-
-
运行线程
OperateThread
-
随调度策略做出的改变,见调度器设计;
-
新增对维修信号
signalMaintain
的判断; -
新增静态方法
OperateThread.maintainElevator()
;OperateThread.maintainElevator()
- 释放运行进程处理队列中的指令到等待队列;
- 释放运行进程装载队列中的指令到等待队列,并修改指令的起始楼层为电梯当前所在楼层。
- 执行所需的
OPEN
OUT
CLOSE
操作。
-
hw3
-
新增守护线程
Daemon
,解决了一些情况下的轮询问题。- 当所有运行线程都结束时,不断对调度线程中等待队列进行
notifyAll
。
- 当所有运行线程都结束时,不断对调度线程中等待队列进行
-
输入线程
InputThread
无需改变。
-
调度线程
DispatchThread
- 引入直达邻接矩阵
direactArrival[11][11]
(以直达需要经过的楼层数为权),当有ElevatorRequest
和MaintainRequest
时,及时更新该矩阵; - 调度策略的改变,见调度器设计;
- 新增两个
Semaphore[]
,分别用来储存每个楼层的电梯的服务许可和每个楼层的电梯的只接人服务许可。
- 引入直达邻接矩阵
-
运行线程
OperateThread
- 在执行
OPEN
操作时,新增获取相应许可的操作; - 在执行
CLOSE
操作时,新增释放相应许可的操作。
- 在执行
基于UML协作图的迭代分析
hw1
hw2
hw3
调度器设计
hw1
-
调度线程结束条件
- 等待队列为空 && 等待队列
isEnd
标记为真。
- 等待队列为空 && 等待队列
-
调度策略:“捎带“
若从等待队列中获取指令不为
null
,则遍历全部未结束的运行进程,若电梯处于Waiting
状态,则执行addPassenger()
;否则,若电梯未满,则执行pickPassenger()
。addPassenger()
- 获取等待队列的队头指令,将其加入该运行进程的处理队列;
- 遍历等待队列,获取其余指令。若该电梯未满,且该指令与此次调用
addPassenger()
加入的第一条指令起始楼层与前进方向分别一致,则将其加入该运行进程的处理队列。
pickPassenger()
- 遍历等待队列,若该电梯未满,且该指令前进方向与电梯当前前进方向一致,且其出发楼层在电梯的途经道路上,则将其加入该运行进程的处理队列。
hw2
-
调度线程结束条件
-
所有运行线程均结束。
运行线程结束条件(下述条件关系为
||
)- 当前电梯的维修信号
signalMaintain
为真 。 - 等待队列为空 && 所有运行线程的(处理队列为空 && 装载队列为空)&& 当前运行线程的处理队列
isEnd
标记为真。
- 当前电梯的维修信号
-
-
调度策略
无变化。
hw3
-
调度线程结束条件
无变化。
-
调度策略:“能捎则捎的贪婪匹配“
若从调等待队列中获取指令不为
null
,则遍历全部未结束的运行进程,若电梯处于Waiting
状态,则执行addPassenger()
;否则,若电梯未满,则执行pickPassenger()
。-
addPassenger()
-
遍历等待队列,获取其中指令。调用
updataFloorTo()
更新该指令的目标楼层。updataFloorTo()
- 若存在原始电梯,则
return false
更新失败; - 若不存在原始电梯,则利用迪杰斯特拉算法求出该请求在当前直达邻接矩阵
directArrival[11][11]
下的最短路径,并将该请求的目标楼层更新为最短路径需要到达的第一个目标楼层。return true
更新成功。
- 更新失败:若该电梯未满,且该电梯可将该指令运输到离目标楼层更近且不越过目标楼层(仅考虑可达性,不考虑电梯当前状态),且该指令与此次调用
addPassenger()
加入的第一条指令起始楼层与前进方向分别一致,则将其加入该运行进程的处理队列。 - 更新成功:若该电梯未满,且该电梯可将该指令运输到目标楼层(仅考虑可达性,不考虑电梯当前状态),且该指令与此次调用
addPassenger()
加入的第一条指令起始楼层一致、前进方向一致,则将其加入该运行进程的处理队列。
- 若存在原始电梯,则
-
-
pickPassenger()
- 遍历等待队列,获取其中指令。调用
updataFloorTo()
更新该指令的目标楼层。- 更新失败:若该电梯未满,且该电梯可将该指令运输到离目标楼层更近且不越过目标楼层(仅考虑可达性,不考虑电梯当前状态),且该指令前进方向与电梯当前前进方向一致,且其出发楼层在电梯的途经道路上,则将其加入该运行进程的处理队列。
- 更新成功:若该电梯未满,且该电梯可将该指令运输到目标楼层(仅考虑可达性,不考虑电梯当前状态),且该指令前进方向与电梯当前前进方向一致,且其出发楼层在电梯的途经道路上,则将其加入该运行进程的处理队列。
- 遍历等待队列,获取其中指令。调用
-
同步块和锁的设置
采用
synchronized(Object)
对对象加锁;采用
synchronized
标记对方法进行同步。
hw1
- 对指令队列、每个电梯的实时状态
Allstate
加锁; - 对指令队列的修改方法进行同步。
hw2
无变化。
hw3
- 对新增的直达邻接矩阵
directArrival[11][11]
加锁; - 对新增的守护进程
Deamon
中的方法进行同步。
Bug分析
出现的bug
-
MaintainRequest
执行不及时将指令类型的判断从调度进程
DispatchThread
提前至输入进程InputThread
。 -
CPU_TIME_LIMIT_EXCEED
减少了不必要的
NotifyAll()
,在一些情况下新增了wait()
,同时增加守护进程Deamon
,实现在运行进程OperateThread
全部结束后唤醒调度进程DispatchThread
。
debug策略
- 在每个线程结束后输出提示语句;
- 在分配指令进电梯时输出提示语句;
- 利用 JProfiler 工具分析死锁;
- 利用 JProfiler 工具查看每个进程的运行时间及CPU时间,针对性地进行优化。
心得体会
经过三次的迭代开发,一个多功能多线程的电梯系统呈现在我们面前。
- 确保线程安全是多线程程序设计中至关重要的问题。
- 在不断的学习与探索中,我明确了死锁的形成原因、如何以及何时上锁,可以合理确定上锁的范围,合理使用
wait-notify
来代替轮询达到降低CPU负载的目的;- 多线程往往会造成一些不可复现的bug,此时就需要仔细分析代码的运行逻辑,在纷繁复杂的表象背后寻找可能造成问题的蛛丝马迹,达到在根本上解决问题的目的,而不可以依赖多线程的不确定性来通过测试。
- 层次化设计是面向对象程序设计中必不可少的关键一环。
- 适当的抽象可以提高程序的可阅读性,增加程序的可拓展性,为迭代开发提供便利;
- 此次项目,我采用了生产者-消费者模式、状态模式,在一定程度上实现了层次化设计。
最后对老师以及助教们的付出致以衷心的感谢!