307
社区成员
发帖
与我相关
我的任务
分享在本单元的三次迭代作业中,我们经历了从单台指定电梯(HW5)到多台自由调度电梯(HW6),再到引入双轿厢与复杂避让机制(HW7)的完整过程。多线程的并发安全与系统性能的动态平衡是本单元的核心挑战。本文将结合这三次作业的迭代经验,对同步机制、调度策略、调试方法及层次化设计进行深度总结。
在多线程环境中,锁的粒度与对象的选择直接决定了系统的正确性与吞吐量。在三次作业的演进中,我的锁机制也经历了由简入繁的过程。
HW5(基础阶段):由于输入指令已经指定了电梯,系统仅需实现基础的生产者-消费者模型。我将锁直接加在 MainQueue 和 SubQueue 内部的集合操作方法上(如 addRequest 和 takeRequest)。这种粗粒度的方法锁在并发度较低时有效保证了数据的原子性。
HW6 & HW7(动态调度阶段):引入自由调度后,共享资源变多。我引入了全局状态板 ElevatorStatusBoard,将锁的粒度细化。调度器读取状态时仅对状态板的局部快照加锁,而不再直接锁定各个电梯的内部队列。
HW7(物理资源竞争阶段):双轿厢模式下出现了针对换乘层(F2)的物理资源互斥。为此,我专门设计了 Coordinator 协作类。此时锁的对象不再是数据结构,而是代表物理空间占用状态的变量 f2Occupied,实现了针对特定楼层的排他性访问。
在编写同步块时,我严格恪守“锁内仅处理内存共享状态,耗时 I/O 与执行逻辑置于锁外”的原则。
在具体的 Wait-Notify 机制中,我将条件判断与业务逻辑深度绑定。例如,在 SubQueue.waitIfEmpty() 的同步块中,我不仅检查队列是否为空、是否结束,还特别加入了 !evadeRequest(避让请求标志)。
while (requests.isEmpty() && maintTask == null && ... && !isEnd && !evadeRequest) {
wait();
}
这意味着,一旦系统要求电梯让出 F2 楼层,无论电梯是否正在因空载而挂机阻塞,setEvadeRequest(true) 调用的 notifyAll() 都能立刻打破 wait() 循环,强制电梯进入避让逻辑。这种将控制信号与同步条件结合的设计,极大提高了线程响应速度。同时,诸如Thread.sleep()和输出语句严禁放入同步块,避免了全局性能雪崩。
系统采用了分离的调度架构。在 HW5 中,Dispatcher 仅作为简单的分发器;而在 HW6 与 HW7 中,调度器真正成为了系统的枢纽。
在交互上,调度器并不直接干预电梯线程的执行逻辑,而是通过黑板模式 (Blackboard Pattern) 进行弱耦合通信。调度器从 MainQueue 获取请求,读取 ElevatorStatusBoard 上的全局快照进行算分,随后将请求压入目标电梯的 SubQueue 中。电梯线程独立轮询自身的队列并向状态板同步最新状态。这种机制彻底解除了调度与执行之间的强同步依赖。
为了在运行时间与耗电量之间取得最优解,我在分配与执行阶段分别设计了贪心策略:
调度分配阶段(动态 Cost 计算):
调度器在分派请求时,依据公式 $Cost = 距离成本 + 负载成本$ 寻找最优电梯。
距离成本评估电梯当前运行方向与乘客请求的顺路程度(不顺路则施加大额惩罚值);负载成本由 (轿厢内人数 + 队列排队人数) * 2 计算得出。乘系数 2 的原因在于,每增加一名乘客,预期将增加一次停靠(开关门时间等效于运行两层楼),这在算分阶段精准量化了时间与电量成本,实现了天然的负载均衡。
物理执行阶段(多维权重进门策略):
当电梯开门允许候乘队列上车时,我摒弃了“先来后到”规则,采用了多维优先级的贪心排序。第一优先级为乘客体重升序,第二优先级为目标距离升序。在额定 400kg 的载重限制下,优先接纳体重较轻、路程较短的乘客,最大化了单次开关门的客流吞吐率,有效减少了电梯的无效启停次数。
得益于前期基础架构的稳固,HW5 在强测与互测中均未出现 Bug。但在 HW6 与 HW7 的极端压力测试中,暴露出了一些深层问题:
资源挤兑引发的系统超时(HW6):在互测中,当遭受“多台电梯被强行置入检修,随后瞬间并发大量请求”的攻击时,前期代码缺乏队列容量上限控制,导致所有请求被堆积在唯一存活的电梯队列中,引发严重超时。修复方案是在 SubQueue 中施加容量硬约束,满载后拒绝接收,迫使调度器休眠重试。
幽灵般的提前退出(HW7):引入双轿厢改造任务后,主线程极易发生误判。当输入流关闭且普通乘客清零时,系统可能直接关闭,而此时某些电梯正在执行 UPDATE,尚未将需要重新分配的乘客退回主队列。最终通过在全局引入 taskCount 变量,严格追踪所有特种生命周期任务,才封堵了这一漏洞。
多线程并发 Bug 往往难以在本地稳定复现。传统的单步断点调试会破坏线程原有的执行时序,掩盖真实死锁。
我的主要调试手段分为两种:
其一,静态查错。利用一些有针对性的提示词,使用ai辅助debug。例如:
这是我们oo第七次作业,是在第五、六次作业基础上的迭代作业,请仔细读题,阅读代码,请帮我检查代码是否有错,有无bug与各种隐患,如TLE,线程安全,稳定性,错误输出,策略错误,检修超时,某限时的操作超时等
其二,搭建评测机。利用ai搭建的评测机,精准判断时间戳等是否有误。
在迭代过程中,层次化设计的价值不仅体现在代码的扩展性上,更在于它是保障线程安全的底层逻辑。

业务逻辑的极致剥离:如果将寻道逻辑、时序记录和物理动作全部糅合在 Elevator 类中,势必需要设置错综复杂的全局锁。在我的设计中,电梯本身被彻底掏空,寻道决策被抽取到 Strategy 类,而指导书严苛的 RECEIVE 时序验证则被封装进 ReceiveTracker 类。各组件之间仅通过局部锁与方法接口通信,降低了死锁风险。
复杂流程的状态机化:针对临时检修与双轿厢转换,系统引入了有限状态机(FSM)。状态转移遵循严格的路径(如 NORMAL -> UP_ACCEPT -> UPDATE -> DOUBLE)。在状态切换的边缘,系统统一执行“强制清客”与“重置起点”操作,将滞留乘客打包退回调度大厅。这种设计避免了碎片化的 if-else 判断,极大提升了并发状态下的系统鲁棒性。
在本单元的开发中,我使用了大语言模型Gemini 3.1 pro作为辅助开发工具。
1. 人机分工模式
我坚持“人类主导架构,大模型辅助推演”的原则。系统的核心类结构、状态机定义及锁的控制粒度均由我独立设计。大模型主要承担逻辑推演的职责,作为验证多线程并发假想的沙盒。
2. 大模型在复杂任务中的优势
在处理幽灵死锁时,大模型拥有强大的状态排列组合分析能力。我总结出了一套高效的提示词工程(Prompt Engineering):首先进行“上下文前置投喂”,将指导书特定的业务规则(如“部件检修开始时强制清空 RECEIVE”)明确告知大模型;随后进行“代码分块”,仅提交涉及死锁的互斥类代码,避免大模型产生逻辑幻觉;最后采用“假设性提问”,引导大模型顺着我设想的执行路径进行验证。这套方法极大缩短了排查死锁锁粒度问题的时间。
3. 遇到的困难与感受
大模型并非万能。如果在开发初期直接要求大模型审查整个工程的数百行代码,它往往会给出似是而非的修改建议,甚至破坏原本正常的线程安全机制。因此,使用大模型的核心能力在于开发者的“精准提问”与“边界划定”。它是一个优秀的结对编程助手,但最终的代码审查权和架构决策权必须掌握在开发者手中。
第二单元的学习曲线陡峭而充实。从第一单元串行思维到本单元并发思维的跨越,初期伴随着对死锁与超时的阵痛。极度考验开发者对于时序控制的敏感度与排错耐心。但随着架构在一次次重构中趋于稳定,亲眼见证系统在高压并发下有条不紊地完成换乘与调度,这种工程实践的成就感是巨大的。
对课程组的建议:
1.希望指导书发布时能更加严谨,不用后续再补充通知,这可能导致代码写了一半需要修改,比较让人头疼;
2.hw6的中测有点过弱,而强测有很多同质bug能触发的点,导致同学可能通过中测而强测因为一个bug几乎拿0分,这一点确实有点不太合适