结束了文字解析的折磨,电梯又是一个新的挑战。第三次作业没进互测房,总结是下次一定。下图是具体设的类:

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

        在三次作业的迭代中,由于引入了多部电梯、全局调度器乃至双轿厢和共享井道,线程安全问题变得越来越复杂。对于锁的选择和同步块的设置,我的整体原则是:锁的度尽可能小,读写共享数据时必须加锁。

  1. 三次作业的演进:

    • 第一次作业: 主要是典型的生产者-消费者模型。锁主要集中在自定义的 RequestQueue 上。通过对 RequestQueue 的关键方法(如 addRequest, flushAllRequests, isEmpty)加上 synchronized 关键字,以 this 为锁,保证了输入线程(生产者)和电梯线程(消费者)交互的线程安全。

    • 第二次作业: 增加了多部电梯和自由竞争/分配调度的逻辑。同步块延伸到了调度器 GlobalDispatcher 中。此时引入了 wait()notifyAll() 机制,在同步块中,如果条件不满足(如队列为空),线程就 wait() 释放锁,避免 CPU 轮询造成的轮询浪费(CPU TLE)。

    • 第三次作业: 引入了双轿厢和暗室改造机制,最棘手的是 F2 层的换乘与碰撞问题。为了防止主备轿厢相撞,我单独设计了 Shaft(井道)类,并在其中设置了 f2Occupied 标志位。通过 acquireF2()releaseF2() 的同步块进行控制,相当于为 F2 层加上了一把互斥锁。

  2. 锁与同步块中处理语句的关系: 同步块内的代码应当是“高内聚”且“短平快”的。例如在 ElevatorThread 中,读取等待队列和乘客列表时:

    Java

    synchronized (queue) {
        // 仅进行状态判断和数据的取出/存入
    }
    

    不能在同步块中调用 Thread.sleep() 或进行耗时的 IO 操作(如大量的 TimableOutput),也不能在持有一把锁的同时去盲目请求另一把锁,否则极易由于资源长期占用而引发 RTLE(运行超时)或死锁。

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

  1. 调度器与线程的交互模式: 我的调度器 GlobalDispatcher 本身也是一个独立运行的线程。

    • 输入端: InputThread 解析请求后,如果是普通乘客请求,直接 dispatcher.addRequest() 丢进全局队列并 notifyAll() 唤醒调度器;如果是维护/改造请求,则直接路由给特定的 ElevatorThread

    • 输出端(分配): 调度器被唤醒后,遍历现有的电梯集合,寻找可以接受请求的电梯,调用对应电梯的内部方法将请求派发过去,电梯内部的 RequestQueue 收到请求后再次 notifyAll() 唤醒正在等待的电梯线程。整个过程是标准的流水线。

  2. 调度策略与多性能指标的权衡: 在前两次作业中,尝试过自由竞争和简单的均匀分配。在第三次作业中,为了解决没进测试房的bug我采用了基于负载的贪心分配策略(类似影子电梯的简化版)。

    • 核心代码逻辑是:double score = e.getPassengerCount() + e.getQueueSize();。调度器会选择当前正在运行、且能到达目标楼层的所有电梯中,score 最小的一部进行分配。

    • 性能适应性: 这种策略综合了时间与电量。选择排队人数和车内人数最少的电梯,能有效避免某一部电梯过度劳累(耗电过高),同时也让请求被尽快处理(减少乘客等待时间)。为了避免 F2 层的死锁情况,调度器中还专门加入了 canMoveTowards 判断,防止双轿厢在交接时互相卡死。

三、 Bug分析与多线程Debug方法

  1. 出现过的典型 Bug:

    • 死锁(Deadlock): 第三次作业中,主轿厢和备用轿厢在争夺 F2 层资源时,如果没有正确释放锁或者在 acquireF2 前后持有其他不必要的锁,很容易双双陷入无尽的等待。

    • 同向拒载(逻辑Bug): 在电梯开关门上客时,如果不及时更新电梯的运行方向,会导致电梯拒载同方向的乘客。代码中 // 核心修复逻辑:接上第一个人的瞬间,立刻将电梯意图方向扭转为该乘客目标方向 正是为了修复这一问题。

    • 轮询与 RTLE: 早期版本中,条件判断写成了 if 而不是 while(true) + wait,导致线程一直在高速空转,评测机直接 CPU 时间超限。

    • 我的 Debug 方法: 多线程的 Bug 往往是不可复现的(与系统的线程调度强相关)。

      主要用的同学的测评机,以及万能的ai。

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

这三次作业让我深刻体会到以下几点。

  • 层次化: 将程序严格分为输入层(InputThread)、分发层(Dispatcher)、执行层(ElevatorThread)和数据承载层(RequestQueue)。每层只负责自己的事情,各层之间通过线程安全的队列进行通信。这样,当第三次作业加入复杂的双轿厢改造和维护操作时,我的调度器结构几乎不需要大改,只需要在 ElevatorThread 中增加复杂的状态机(如 NORMAL, UPDATE, RECYCLE, REPAIR 等)即可。

  • 线程安全建立在封闭性之上: 只要把需要共享的状态(如乘客队列、F2井道占用情况)严密封装在对象内部,只对外暴露 synchronized 的方法,就能将大部分并发问题防患于未然。

五、  大模型的使用心得

在本次多线程电梯的任务中,我尝试将大语言模型(主要使用 Gemini 3.1 Pro )作为我的“编程结对伙伴”,感受颇深。

  1. 分工模式: 我主要负责架构设计、核心调度算法的设计以及状态机的状态划分;大模型则被我用作语法向导、样板代码生成器和调试法的倾听者

  2. 大模型的优势:

    • 概念解析清晰: 在刚接触 wait()notifyAll() 的底层原理和虚假唤醒(Spurious Wakeup)问题时,大模型的解释比单纯看官方文档直观得多。

    • 正则表达式与接口生成: 快速生成繁琐的解析逻辑或简单的 Bean 类。

    • 并发机制答疑: 当遇到某些线程挂起不执行的问题时,把相关代码块抛给大模型,它能迅速指出可能的死锁隐患(例如嵌套锁或者忘记 notify)。

  3. 遇到的困难与局限:

    • 容易思考不全: 在面临电梯这样高度复杂的上下文时(特别是涉及到第三次作业中检修、双轿厢改造与正常调度的耦合),大模型很容易犯错。如果你问它如何修改电梯状态机,它常常会给出一个只能实现当前目标,不考虑各种例外情况的代码,导致整个系统的另一端出现bug。

    • 某些模型本身数据集就不对: 比如ds和豆包,思考半天甚至会给你教错的知识。

  4. 总体感受: 大模型能极大提升编码效率并迅速扫除知识盲区;但在面临复杂系统工程时,它不太能替代人类。多线程的难在于脑海中必须知道线程要怎么交互,大模型在误解题意后会给你一个错误的代码。

六、 第二单元真实体验与课程建议

真实体验: 其实我的作业很多都是大模型教我写的,大模型越来越厉害了,不用几分钟一份正确里有待商榷但能跑的代码就映入眼帘。照这样看的话不说提供思路了,一个好的大模型连完整构思以及debug也是能轻松帮你解决。我目前为了写一份比较好的作业最需要的就是看懂代码并判断是否符合题意的能力。

给课程的建议:要不老师上课也用ai辅助?说不定有奇效。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐