2026 OO Unit2 博客总结

刘梦琨-24231215 2026-04-25 10:39:07

OO 第二单元学习总结:多线程电梯调度中的同步、分层与策略


OO第二单元围绕多线程协作展开:从指定乘客目标电梯,到新增维修指令和自主设计分配策略,到最后一次引入双轿厢。课程作业要求要保证线程安全,不会产线程冲突,又要在响应时间无效移动之间做权衡。本单元的作业难度较大,代码量显著增加,且涉及到多线程会带来意想不到的bug。


一、同步块与锁的配置及与语句的关系

第6次作业框架

第6次作业框架

img

第7次作业框架

1.1 RequestPool:以内置锁保护队列与条件等待

全局池、井道入站池以及每台轿厢的专用池均复用 RequestPool。所有对 Deque<PersonRequest> 的读写、closed 标志的修改,以及 wait / notifyAll,都落在同一个 synchronized 实例方法上,即以当前 RequestPool 对象为监视器锁。

主要示例:

  1. addGlobalRequest / takeOneRequest:生产者与消费者互斥访问队列;消费者在队列为空且未结束时 wait(),生产者在入队后 notifyAll() 唤醒等待线程。
  2. setInputFinished:将 closed 置真并 notifyAll(),使仍在 wait 的线程能读到「结束」语义并返回 null 或空队列,避免永久阻塞。
  3. fetchAllUnassigned:一次性拷贝并清空队列,供井道线程批量路由,仍在同一把锁下完成,避免与并发 add 交错导致丢数据或重复。

1.2 ShaftCoordinator:井道级协作状态与移动前屏障

ShaftCoordinatorshaftModeauxEnabled、主/备楼层、shutdown 等放在 synchronized 方法中读写,并在 setShaftModesetAuxEnabledupdateFloorbeforeMove 等处 notifyAll()

与块内语句的关系

  • waitIfDisabled:备轿厢在「暗室」未启用时,在协调器锁上 wait,直到 setAuxEnabled(true)markShutdown 唤醒,保证启用标志与线程继续执行原子地可见。
  • beforeMove / canMove:移动前在循环中 wait,直到几何约束(双轿厢下主在辅上、不共层、楼层上下界等)满足。锁内维护的是两台轿厢共享的约束条件,语句只读写字段并决定阻塞或放行,不把耗时 IO 放进同步块。

1.3 使用Java中特殊数据结构:ConcurrentHashMapConcurrentLinkedQueue

DispatchelevatorShaftStatusMap使用 ConcurrentHashMap,维保/改造/回收队列使用 ConcurrentLinkedQueue。这类结构用于高频、单操作原子的入队、出队、put,避免与 RequestPool 的监视器锁嵌套;ElevatorStatus 本身是不可变快照,适合被多个线程读取以做全局分配启发式。

小结:锁的选择遵循共享可变状态谁维护、谁持锁——队列一致性由 RequestPool 的monitor保证


二、调度器设计及其与线程的交互

本项目的调度是分层的,不是单一中央循环。

2.1 全局层:Dispatch 调度线程

  • 输入:从全局 RequestPool 阻塞或轮询取 PersonRequest(与 InputThread 的生产关系为典型的生产者–消费者)。
  • 输出:根据 assignElevator 得到目标井道id,将请求写入该井道与ElevatorShaft共享的入站 RequestPoolshaftRequestPools)。
  • 结束联动:当全局池空且输入结束,对所有井道入站池调用 setInputFinished(),把「结束」沿流水线向下传递。

特殊请求(MaintRequest / UpdateRequest / RecycleRequest)不经过全局乘客池,而是由 InputThread 写入Dispatch侧各 ConcurrentLinkedQueue,由对应主/备轿厢在运行循环中 poll

2.2 井道层:ElevatorShaft 线程

  • 职责:从入站池 drain / takeOneRequestWithTimeout 聚合请求,按当前 ShaftCoordinator 模式调用 chooseTargetPool,分流到 **mainPoolauxPool**;无法立即分流的进入 deferredRequests 并在后续循环重试。
  • 与轿厢线程的关系:井道线程启动主/备两个 Elevator 线程(shaft-k-main / shaft-k-aux),在本地队列与延迟队列清空且输入结束后 finishSubPoolsjoin 子线程,保证先停源再停消费者的关闭顺序。

2.3 执行层:Elevator 线程与 Decision

每台轿厢独立线程从自己的RequestPool 拉请求、receivewaitingRequests,再调用 Decision(实现为 LookDecision)得到 OPEN_DOOR / MOVE_* / WAIT / IDLE 等。移动前通过 ShaftCoordinator.beforeMove与井道内另一台轿厢协作。

线程交互一览

线程与其它线程的协作方式
input-thread写全局池;写 Dispatch 的特殊队列
dispatch-thread读全局池;写井道入站池;读 ElevatorStatusConcurrentHashMap
shaft-k读入站池;写主/备池;join 轿厢
shaft-k-main / aux读各自池;reportStatusToDispatchShaftCoordinator 上 wait/notify

三、调度策略及对时间、能耗类指标的适应

3.1 全局分配:assignElevator

对每条 PersonRequest

  1. ElevatorStatus.canTakeRequest 过滤载重不可接的井道(快照中的 currentDispatchWeight 含本机负载、延期、池中与等待队列权重,使全局侧对「是否还能接人」有保守估计)。
  2. 将请求方向与轿厢当前方向比较,优先同向或 WAIT/IDLE 的井道,减少「先接人再折返」带来的时间浪费。
  3. 在候选集合内按与出发层的距离选最近;距离并列时用 ThreadLocalRandom 随机打破对称,减轻多井道固定偏向导致的负载不均(间接影响平均等待与个别电梯空驶)。

3.2 井道路由:chooseTargetPool

DOUBLE 模式下以换乘层(实现中为 F2 索引边界)划分低区/高区:低区进副轿厢、高区进主轿厢、跨区默认进主轿厢。

改造(UP_ACCEPT/UPDATE)与回收(REC_ACCEPT/RECYCLE)阶段,chooseTargetPool 返回 null 或收紧可服务楼层,与状态机一致,避免在非法阶段把乘客派到无法服务的轿厢。

3.3 单轿厢微观调度:LookDecision(LOOK)

在已 receivewaitingRequests 与车内乘客上执行 LOOK:优先沿当前方向服务同向目标;必要时反向;开门决策结合下客与可上车列表(含载重裁剪与方向一致的挑选)。WAIT 在仍有潜在输入时使用短路径 pullNewRequests 与超时等待配合(见 Elevator 中常量),在及时性忙等耗电之间折中。


四、Bug 分析与多线程调试方法

1. 电梯关门时间过短

代码调用EventPrint输出关门信息,但是EventPrint中的方法是加锁的,导致在高并发情况下,线程处于等待锁的状态,无法及时输出关门信息。导致输出中实际关门时间与代码中开始等待时间不一致。解决办法,通过调整代码顺序,先输出关门信息,再进入等待状态,确保输出的时间与实际关门时间一致。

2. 乘客需求未完成

当电梯状态发生变化时,电梯会将未完成的请求重新放入全局请求池中,进行重新分配。但是当输入结束时,电梯如果将未完成的请求放入全局请求池中,但由于输入已经结束,调度器不会再从全局请求池中取出请求进行分配,导致这些请求无法被处理。

3. TLE问题

分配策略的不合理导致超时问题,主要为电梯分配策略只将请求分配给NORMAL状态的电梯,导致在极端情况下,所有请求都被分配给同一台电梯,造成该电梯过载,无法及时处理请求,最终导致超时。解决办法是优化分配策略,将请求分配给所有可用的电梯,而不仅仅是NORMAL状态的电梯,以实现负载均衡。


五、线程安全与分层设计的体会

5.1 线程安全

  • 线程安全:多线程并发下,保证数据不发生冲突、不出现竞态条件、死锁等问题,且能正确响应输入和状态变化。
  • 实现上混合了monitor(RequestPoolShaftCoordinator)与并发集合(队列)
  • ElevatorpassengerswaitingRequests 等主要由单轿厢线程访问;与调度器的交叉点通过 Dispatch 的线程安全结构与快照上报完成,避免到处加锁。

5.2 分层设计

  • I/O 层InputThread 只解析与投递,不决定电梯行为。
  • 全局调度层Dispatch 只做哪一口井与特殊请求入队。
  • 井道编排层ElevatorShaft 只做哪一台轿厢的池与延迟重试。
  • 执行与策略层Elevator + Decision 管移动与门;ShaftCoordinator 管井道内横切的安全约束。

六、大模型使用心得

  1. 多线程代码复杂性较高,尤其是涉及多个线程之间的交互和共享状态时。使用大模型在分析和总结多线程设计原则、常见模式以及潜在问题方面非常有帮助,可以提供系统性的指导和建议。
  2. 使用大模型了解了Java中常用的线程安全工具和数据结构,如ConcurrentHashMapConcurrentLinkedQueue等,以及如何合理使用synchronized块和条件变量来实现线程间的协调。
  3. 大模型在分析调度策略时,能够帮助理解不同策略对系统性能的影响,以及如何在响应时间和能耗之间进行权衡。

七、总结

OO第二单元的多线程电梯调度项目是一个综合性的挑战,涉及到线程安全、分层设计、调度策略等多个方面。通过合理使用Java的同步机制和并发数据结构,以及设计清晰的分层架构,实现一个高效且线程安全的电梯调度系统。前两次作业难度适中,但是第三次作业引入了双轿厢和更多的调度细节,情况独特,超越生活的情况导致分配算法不好进行处理。希望可以对第三次作业内容进行修改。

...全文
175 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

307

社区成员

发帖
与我相关
我的任务
社区描述
2026年北航面向对象设计与构造
java 高校
社区管理员
  • 孙琦航
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧