第二单元(hw5-hw7)多线程电梯系统总结博客
一、三次作业的总体演进与我的实现主线
第二单元三次作业的核心都是“多线程实时电梯系统”,但难度是层层递进的:
- hw5:以“多电梯并行+基本捎带”为主,重点是线程模型搭建与基础正确性。
- hw6:加入临时检修(MAINT),核心从“会跑”变成“在动态约束下持续正确”。
- hw7:加入双轿厢改造/回收(UPDATE/RECYCLE)与同井道协同,复杂度从多线程进一步升级为“多线程+多状态机+多资源竞争”。
我自己的实现路线是:
- 先把“输入线程—调度线程—电梯线程”的生产者/消费者骨架搭稳;
- 再把RECEIVE约束、维护流程、乘客中转这些正确性问题逐步补齐;
- 在hw7中引入井道状态总管,把“局部电梯逻辑”提升为“井道级协同逻辑”。
二、同步块设置与锁选择:三次作业的总结与对比
1.锁结构
三次作业中,核心共享对象与锁大致可分为四类:
- 队列锁:保护请求队列、结束标志、wait/notify协作。
- 乘客映射锁:保护“乘客ID->电梯ID”的RECEIVE映射。
- 载重锁:保护电梯内乘客列表与总重量,保证上下客与载重判断一致。
- 井道状态锁(hw7新增):保护双轿厢模式、F2占位令牌、特殊流程占用状态。
2. 同步块与业务语句的关系
锁不是给“代码块”加保险,而是给“状态不变量”加保险。
(1)队列锁:保证“检查条件+等待/唤醒+取出请求”的原子性
在调度器/电梯线程中,典型结构是:
- while(队列空&&未结束)wait
- 条件满足后取请求
这要求“看条件”和“睡眠”在同一把锁下,否则会出现“刚检查完为空就错过通知”的经典竞态。
(2)映射锁:保证RECEIVE语义的一致性
RECEIVE 的关键是:同一乘客在同一阶段只能归属一个电梯。若多个线程交错清理/写入映射,容易出现重复RECEIVE、无法重新RECEIVE等错误。hw6/hw7中对映射的增删都做了集中同步,避免了这个问题。
(3)载重锁:保证“门状态与载重约束”一致
关门前必须满足载重约束。上下客会改变总重,因此“上下客修改总重”与“关门前检查总重”必须通过同一个同步对象协调。这样才能保证不会出现“刚判断可关门但实际已超重”的瞬时不一致。
(4)井道锁(hw7):保证双轿厢不冲突
hw7 的关键资源是同井道F2。两个轿厢都想进入/占用F2时,必须有互斥规则。通过井道状态管理器统一维护占位令牌并使用wait/notify,才能避免“同层冲突”与“互相等待”。
3. 三次作业锁策略的演进
- hw5:以队列同步为主,锁粒度较粗,目标是“先正确跑起来”。
- hw6:增加维护状态后,锁开始围绕“状态转移一致性”设计(RECEIVE清理、重分配、退出时机)。
- hw7:进入“多资源协同”,除了线程安全,还要做资源调度安全(井道、换乘层、主备轿厢启停)。
三、调度器设计、线程交互与调度策略(含性能指标适配)
1. 调度器设计的三次迭代
hw5:指定电梯,调度器近似“转发器”
输入已给BY-电梯ID,调度器主要负责:
- 从全局队列取请求;
- 输出RECEIVE;
- 投递到指定电梯队列。
hw6:自主分配+检修绕行
调度器职责升级为:
- 自主选择电梯(轮转+跳过维护中/待维护电梯);
- 跟踪MAINT进行数,避免“输入结束但维护未结束”时提前停机;
- 处理中途下客/维护清客后的重新分配。
hw7:井道级调度+主备轿厢协同
新增能力包括:
- 根据请求楼层区间和井道模式(单/双)分配主轿厢或备用轿厢;
- 对 MAINT/UPDATE/RECYCLE统一做“特殊流程计数”,保证系统退出时机正确;
- 跳过特殊占用井道,降低“分给不可服务对象”的概率。
2. 调度器与线程交互
系统线程交互链路大体是:
- InputProcessor读取请求,写入全局队列;
- JobDispatcher 从全局队列消费后,写入电梯专属队列;
- LiftHandler 执行运动/开关门/上下客;
- 若中转、检修、改造等导致请求未完成,电梯线程再把请求回流到全局队列,由调度器二次分配。
这是典型“多阶段生产者-消费者 + 回流”的架构。
3. 调度策略与性能指标的适配
课程性能指标核心是运行时间、平均完成时间、耗电量。在策略上的取舍是:
(1)时间指标
- 尽量将请求分配给当前可服务的井道/轿厢,减少无效等待;
- 特殊流程优先入队,缩短维护/改造响应时间;
- hw7 中主备轿厢按楼层区间服务,减少跨区折返。
(2)电量指标
- 在同次开门窗口内批量完成上下客,减少额外开关门;
- 避免空跑(无有效请求不启动移动);
- 双轿厢后通过分区服务减少长距离无效移动。
(3)多指标平衡
若过度追求最短等待,可能导致频繁停靠和开关门;若过度省电,可能增加平均完成时间。我的策略更偏“稳健中庸”:
- 先保证正确性和退出安全;
- 在此基础上减少明显空跑与冲突;
- 不做过度激进、易破坏线程安全的优化。
四、出现过的bug与我的多线程debug方法
1. 我遇到/重点处理过的典型bug
调度器提前退出(hw6)
现象:输入结束且全局队列短暂为空时,调度器退出;但此时某电梯仍在维护流程,后续还会回流未完成乘客,导致请求无人处理。
本质:退出条件没有把“系统动态状态(维护中/特殊流程中)”纳入判定。
修复思路:退出条件加入维护/特殊流程计数与输入结束联合判定。
维护开始后未进电梯乘客处理不完整
现象:MAINT1-BEGIN后,部分已分配但未上车的乘客没有被正确回流,导致“逻辑上存在请求,系统中却丢失”。
本质:RECEIVE结束与请求实体回流没有同步完成。
修复思路:清理RECEIVE映射+回流全局队列+唤醒调度器。
RECEIVE映射残留
现象:乘客已中途下车,但映射未清理,后续不再输出新的 RECEIVE,触发约束错误。
本质:状态机推进了,映射状态没推进。
修复思路:中转下车、维护清客、改造/回收清客都统一调用清理逻辑。
双轿厢 F2 协同冲突(hw7)
现象:主备轿厢在 F2 发生竞争,轻则等待放大,重则相互卡住。
本质:共享资源(F2)没有统一仲裁器。
修复思路:引入井道状态管理器 + F2占位令牌 + 让行机制。
2. debug 方法
- 先做状态机对账,不先盯代码细节:把请求从输入到完成的完整链路写成时序图,先找“状态断裂点”。
- 构造最小复现数据:少量请求+精确时间戳,专打一个条件(如“输入刚结束+维护未结束”)。
- 加结构化日志而不是乱打日志:按“线程名/请求ID/状态转换前后”打印。
- 关注不变量:如“RECEIVE映射存在 <=> 乘客有当前归属”;任何违反都立即追踪。
- 回归测试分层做:先单电梯,再多电梯,再维护,再双轿厢,最后混合压测。
五、对“线程安全 + 层次化设计”的理解
线程安全:不是“全都加锁”,而是“定义所有权”
- 谁拥有状态,谁负责修改;
- 跨线程共享状态越少越好;
- 必须共享时,围绕不变量设计锁边界。
比如电梯运行细节尽量由电梯线程单写,调度器不直接改电梯内部细节;调度器只通过队列和少量状态接口交互。
层次化设计:把“策略”和“动作”分开
当前系统拆成:
- 输入层(InputProcessor)
- 分派层(JobDispatcher)
- 执行层(LiftHandler)
- 规则/状态层(LiftController、ShaftStateManager)
- 细节服务层(PassengerManager、DoorOperator、FloorMovement、LiftPassengerFlowService)
这样做的好处是:
- 新需求(MAINT/UPDATE/RECYCLE)可以落在相对独立的层;
- 修改冲击面更小;
- debug 时能快速定位是“决策错”还是“执行错”。
六、大模型使用心得(我使用 Copilot 辅助)
1. 模型与分工
本单元主要使用GitHub Copilot做辅助。我的分工是:
- 我负责题目概念理解、状态机设计、代码测试和debug、设计优化策略;
- Copilot负责样板代码补全、重构建议、增删代码注释、代码风格优化等。
整体来讲,大模型是“副驾驶”,不是“自动驾驶”。
2. 大模型优势
面对多线程电梯这类复杂任务,大模型信息整合速度快,而且面对大量代码量可能导致的基础错误有很好的纠错能力;此外重构效率高,hw7为了代码风格重构LiftHandler类时,把大类拆服务类时很省时间。
此外,能快速产出代码架构梳理,分析报告、测试方案骨架,节省很多时间。
3. 大模型缺陷
最大问题是模型记忆功能还是不够完善,直接vibe coding会出现一些意想不到的低级错误,离不开人工测试和逻辑补完()。而且大模型面对一些细化的bug时,debug策略不是很好,还是要靠人力。
七、第二单元真实体验、感受与建议
这一单元是我在OO课程里压力最大的阶段之一(中间hw6进了一次o房心态很爆炸)。难点主要在规则多、状态多、线程多;bug不稳定复现;小改动会引发连锁反应等等。
但也吸取了很多教训,比如意识到写一个性能良好的评测机真的很重要()。
结语
回看hw5到hw7,最大的变化不是“写了更多代码”,而是开始真正理解:
- 多线程系统首先是状态系统,其次才是代码系统;
- 线程安全首先是边界设计,其次才是同步语法;
- 性能优化首先是正确性稳定后的工程迭代。
第二单元确实很难,但也确实让我在并发编程、系统分层与工程思维上迈过了一个关键台阶。