Unit 2:多线程电梯的并发调度实践

高晨凯-24373278 2026-04-29 22:45:00

一、同步块的设置与锁的选择

处理多线程时,锁的粒度与选择直接决定了系统的安全性与并发性能。随着并发的复杂度不断提高,锁策略也必须随之不断演进。

1. 核心数据结构的同步(WaitQueue 锁)

为了保护需要被多部电梯高频访问的请求队列,将其封装为 WaitQueue 类,将所有对内部 ArrayList<Request> 的增删查改操作(如 addRequest, getOneRequest)加上 synchronized 关键字。
ElevatorScheduler 在访问队列时,锁的对象是特定的队列实例,实现了天然的互斥,避免了并发修改异常。同步块中仅包含最基础的集合操作,确保了极短的持锁时间。

2. 全局调度状态控制(AtomicInteger

随着更多操作会影响请求的 reiceive 状态,为了解决程序何时安全终止的问题,引入了 RequestCounter。这里没有使用传统的对象锁,而是选择了 AtomicInteger。通过原子操作记录当前系统内尚未完成的请求总数。
这种无锁(Lock-free)设计不仅减少了阻塞,还在处理双轿厢换乘(请求需重新投递)时,避免了由于加锁顺序不当导致的死锁风险。

3. 双轿厢避让机制(ShaftLock 井道锁)

在第七次作业的双轿厢设计中,F2 成为了 A、B 轿厢共用的换乘层(冲突区)。我为6个井道分别设计了一个独立的 Object shaftLock。当电梯尝试进入 F2 或在 F2 开门时,必须先获取所在井道的锁。同步块内包裹的是电梯在 F2 停留的整个物理过程。这保证了同一井道内绝对不会出现双轿厢相撞的情况,通过物理隔离保证了线程安全。

二、 调度器设计与调度策略分析

本单元我采用了生产者-调度器-消费者的三级模型。

1. 交互模式

InputThread 负责读取请求并放入 GlobalQueueScheduler 作为独立线程,不断从全局队列取出请求,计算后分配给特定电梯的 WaitQueue;电梯则作为消费者,只关心自己队列里的请求。

2. 双轿厢的调度适配

在第七次作业中,Scheduler 的作用尤为关键。它不仅负责负载均衡(通过对比各井道内的请求堆积量进行 allocate),还充当了类似“路由网关”的角色。当发现双轿厢井道时,Scheduler 根据请求的起始楼层,将去往高层的分配给 A 轿厢队列,去往低层的分配给 B 轿厢队列。

3. 策略与性能指标

电梯内部的运行策略被抽象为一个独立的 Strategy 类,实现了纯粹的 LOOK 算法。在面临时间与电量双重考核时,LOOK 算法能最大限度减少电梯的无效折返,降低了运行耗电量。而 Scheduler 的均衡分配策略则保证了所有电梯都在工作,避免了一个电梯被塞入太多请求而其他电梯空闲的现象,有效缩短了乘客的最大等待时间。

三、 Bug分析与Debug方法

多线程的Bug通常具有难以复现的特点,在本次迭代中主要遭遇了两个棘手问题:

1. 死循环与假死(F2 层的无尽开关门)

在双轿厢作业中,曾遇到 1 号电梯在 F2 层不断开收门的死循环。经过排查,发现是 Strategy 与双轿厢的换乘逻辑产生了冲突。电梯到达 F2 时,策略判定内部有人必须开门,但由于乘客的目的地并不是 F2(而是需要换乘),导致无人出电梯,随后关门,再次被策略判定需开门。
修复方法是将电梯的运行范围(minFloor, maxFloor)传入 Strategy,只有当乘客目标超出当前管辖范围时,才在 F2 强制开门换乘。

2. 程序无法自动终止

在处理双轿厢换乘时,输入线程已结束,但程序始终无法退出。原因是换乘乘客在 F2 下车时被视为了“中间态”,此时 RequestCounter 并未清零,但 Scheduler 陷入了 wait(),无人唤醒。修复方法是在乘客真正到达最终目的地(OUT-S)时,不仅释放计数,还强制调用 globalQueue.notifyAll(),唤醒调度器进行结束清算。
Debug心得:面对多线程,断点调试往往会改变线程的执行时序。主要依赖打印日志,通过 TimableOutput 梳理时间戳,观察各线程的状态转移。

四、 线程安全与层次化设计

这三次作业在以往课程的基础上进一步强调了“高内聚、低耦合”同样是保障线程安全的基石。

1. 层次化设计

Input -> Scheduler -> Queue -> Elevator -> Strategy,数据流向单向且清晰。
Strategy 作为一个纯逻辑类,完全剥离了多线程环境,它没有任何锁,只负责根据输入参数输出 Advice。这种设计在后续增加双轿厢逻辑时,几乎可以不用担心有可能破坏基础的线程安全。

2. 线程安全理念

尽最大可能缩小共享变量的范围
让每个电梯维护自己的局部状态(当前楼层、方向),只在必须交换数据(拿取请求)时才与外界产生加锁交互。

五、大模型的使用心得

1. 使用的模型名称

Gemini 3 Thinking / Pro

2. 分工模式

我主要负责系统宏观架构的设计、面向对象类的抽象以及一些策略的基础思路。而 Gemini 则进行代码的具体补全落地以及一些逻辑的验证。

3. 大模型的优势

在面对多线程电梯这样复杂的任务时,大模型在日志分析和死锁/死循环定位上展现了极高的敏锐度。例如前面提到的 F2 层死循环和程序无法退出的 Bug,把冗长的控制台输出和相关类的代码喂给 Gemini 时,它能从大量无规律的输出中,指出 wait()notifyAll() 在换乘逻辑中的配合出现了断层,并给出了 releaseAndNotify 的重构思路。

4. 遇到的困难

大模型有时会缺乏对工程全局上下文的感知。例如,它可能会建议在 Strategy 中直接调用 Elevator 的某个属性,却忽略了之前在设计上刻意让 Strategy 保持独立解耦。这就需要在 Prompt 中清晰地界定需要保护的结构,并针对性地提供代码片段。

5. 使用感受

大模型极大缓解了面对多线程 Bug 时的挫败感,不仅仅作为一个代码生成工具,有时候更是一个稳定的讨论对象,帮助我理清思路。但前提是,使用者本人必须对自己的架构有全面的认知,大模型提供的是一些具体内容战术支援,而提纲挈领的蓝图依然需要自己掌握。

六、真实体验、感受与建议

第二单元无疑是OO课程的一道分水岭。从第一单元算法层面大量扩容与迭代到第二单元系统与架构层面的重构,其在理解和运用上的难度提升是不言而喻的。多线程的高并发,其不可预测性和大量数据的吞吐在修复 Bug 时提出了严峻的考验,在 HW7 的迭代中其对原有结构的变动使我最终也没有理清进程的启动、等待与结束。

  • 建议:

希望适当增加公测的强度和数据披露,以免到强测、互测时常常差距很大,也难以决定在性能方面的取舍。

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

304

社区成员

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

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