第二单元电梯调度博客

侯博渊-24373120 2026-04-27 14:10:34

面向对象第二单元总结博客


一、同步块与锁的设计

在多线程电梯系统中,同步控制是正确性的基石。我的同步设计经历了从粗糙到精细的迭代。

1. 锁的选择

我主要使用了两种锁机制:

  • synchronized 关键字:用于 RequestQueueRequestTable 等共享队列,保护 puttakeretrieve 等临界操作。之所以选择它,是因为这些数据结构被频繁访问,synchronized 语法简洁,且通过 wait/notifyAll 很容易实现阻塞等待。
  • ReentrantLock:用于 ElevatorPair 中的双轿厢碰撞检测。这里需要手动控制锁,因为碰撞检查涉及多个共享变量primaryFloorsecondaryFloor 等,ReentrantLock 的 lock()/unlock() 可以精确控制范围,并且可以创建条件变量,比 synchronized 灵活。

2. 同步块与操作的关系

  • RequestTable 中的所有公共方法都是 synchronized,保证了等待队列的一致性。例如 retrieveSameDirection 必须在取出乘客时原子地判断方向和移除元素,防止两个电梯线程同时接走同一乘客。
  • ElevatorThread 中的 onboard 列表通过 synchronized (onboard) { ... } 锁保护,保证对轿厢内乘客的修改上下客是线程安全的。特别在 handleDropOffhandlePickUp 中,对 onboard 的遍历和修改必须加锁,否则会导致并发修改异常或重量计算错误。
  • hasMovePermission 中虽然没有直接加锁,但它读取了 onboard.isEmpty()table.isEmpty() 等需要同步的信息。我的做法是让这些 getter 方法自身同步,从而间接保证可见性和原子性。

3. 从历史修复看同步缺陷

早期版本中,onboard 的修改没有完整加锁,导致在 handleDropOff 中可能发生并发删除。后来我统一对 onboard 的迭代器操作加锁,问题才消失。这印证了:只要涉及共享可变集合的遍历,必须同步。


二、调度器设计

1. 调度器的角色与线程交互

调度器 Scheduler 本身是一个线程,居中协调:

  • InputThread 将乘客/检修/改造/回收请求放入 inputQueue
  • 调度器从 inputQueueglobalQueue中转重分配乘客中取出请求。
  • 调用 chooseElevator(p) 选择电梯后,输出 RECEIVE 并将 Person 放入对应电梯的 RequestTable
  • 对于特殊请求MAINTUPDATERECYCLE,直接调用电梯的 acceptMaint/acceptUpdate/acceptRecycle 方法触发状态转换。

交互的关键在于 wait/notify

  • 电梯在 table.isEmpty() && !end 时会调用 table.wait() 进入等待,调度器 addPerson 后调用 table.notifyAll() 唤醒。
  • 调度器在找不到可用电梯时,会 wait 自身,等待电梯完成特殊流程(如维修结束)后通知 Scheduler.class.notifyAll() 唤醒。
  • 这种设计避免了轮询,符合课程要求。

2. 调度策略与性能适应

我的调度策略采用“最小负载优先”:遍历所有可用电梯,计算“轿厢内人数 + 等待队列长度”作为负载,选择负载最小的电梯。这个策略简单但有效,能够自然平衡系统负载,缩短平均完成时间。

在适应性能指标方面:

  • 运行时间:通过让电梯在空闲时 WAIT 避免空跑,减少无效移动。
  • 平均完成时间:最小负载分配使得乘客尽快被接走。
  • 耗电量:避免电梯无意义空驶,同时在 Strategy 中增加了“双轿厢模式下 F2 无任务必须立刻离开”的逻辑,减少阻塞等待的电量浪费。

不过我的策略仍有提升空间,例如没有显式预测电梯到达时间,只是简单比较负载,但整体性能已经能满足公测和互测要求。


三、Bug 与 Debug:

本次作业中我遇到了两个棘手的多线程 bug,分别对应 RECEIVE 约束违规和双轿厢碰撞,debug 过程让我深刻体会到时序问题难以复现的特点。

Bug 1:[49.08] Elevator 3 cannot move because it did not receive any passenger

  • 现象:在备用轿厢回收完成后,主轿厢仍然输出了 ARRIVE,但评测机认为它此时已无未结束的 RECEIVE。
  • 根因ElevatorMover.move() 中,sleep 前就修改了 currentIndex,且 sleep 后没有再次检查移动权限。sleep 期间备用轿厢完成回收secondaryActive 变为 false,主轿厢丧失移动资格,但醒后仍输出 ARRIVE。
  • 修复:将 currentIndex 的更新推迟到 sleep 之后,并在 sleep 后调用 hasMovePermission()。若权限失效,则清除预定楼层并直接 return。

Bug 2:[15.678] Elevator 4 cannot arrive at floor F2 when its partner already at this floor

  • 现象:批量回收时,主轿厢与正在回收的备用轿厢在 F2 发生冲突。
  • 根因ElevatorPair.canMoveTo() 中,备用轿厢的交叉换位逻辑写反。主轿厢分支判断交叉时 return true允许,备用轿厢对称处却是 return false,导致备用轿厢被错误阻挡在 F2,随后主轿厢到达产生碰撞。
  • 修复:将备用轿厢交叉换位处的 return false 改为 return true,恢复对称性。

Debug 方法

  • 日志推理:通过分析评测机的输出时间戳,结合代码逻辑推测出问题时段的状态变化。
  • 竞态假设:评估可能的状态切换窗口,针对性地添加防御代码。
  • 借助大模型分析:将错误报告和关键代码片段交给大模型,它会给出可能的时序和根因,我再去验证。这极大提高了定位效率。

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

层次化设计是将系统分解为独立的逻辑层,每层只关心自己的职责,通过明确接口协作。我的电梯系统分为:

  • 输入层InputThread:只负责解析输入,放入队列。
  • 调度层Scheduler:负责分配乘客、处理特殊请求。
  • 电梯层ElevatorThread:负责电梯运行、状态转换。
  • 策略层Strategy:根据电梯状态和请求表给出行动建议。
  • 运动层ElevatorMover:执行具体的移动、开关门、上下客。
  • 协调层ElevatorPair:管理双轿厢碰撞和换乘。

这种分层让各层职责清晰,线程安全可以局部化。例如,所有对 onboard 的修改集中在 ElevatorMover 中,通过锁保护;调度器只修改 RequestTable,电梯只读取它。层次化也使得 bug 修复可以精准定位到某一层,比如碰撞问题只需修改 ElevatorPair 的判断逻辑,而不影响其他部分。

线程安全的核心体会

  • 尽量减少共享数据的范围,能用不可变对象就用不可变对象。
  • 对共享集合的读和写都必须加锁,即使是遍历。
  • 多线程下的状态转换要有防御性检查,例如我在权限检查中多次补充“假 DOUBLE”修正,就是为了防止瞬息万变的状态。

五、大模型使用心得

模型名称:DeepSeek

分工模式

  • 我会把困惑的现象、报错信息、核心代码片段输入,它给出可能的原因和修复思路。
  • 我的角色是“:判断大模型建议的合理性,将其转化为具体代码,并验证效果。
  • 在遇到 bug 时,我会描述症状,大模型提出若干根因假设,我根据断点或日志去排除和确认。

优势

  • 快速提供多线程竞态的常见模式,如“sleep 后权限失效”、“交叉换位逻辑不对称”这类问题,大模型能迅速定位。
  • 能给出具有一定结构性的修复方案,节省了翻阅文档和搜索的时间。
  • 对于状态机扩展等重复性工作,可帮忙补充代码片段,我只需微调。

困难

  • 大模型有时提供的方案过于理论化,忽略了作业的具体约束),需要我二次适配。
  • 建议不够精确时,容易引导我走弯路,例如最初建议在 canMove 中加检查,但没有抓住“sleep 前修改变量”的根本问题,后来还是靠反复对话才找到真正原因。
  • 对复杂逻辑的理解可能产生偏差,给出的修复代码需要仔细审核。

感受

大模型是一个有力的伙伴,但它不能替代自己对代码的彻底理解。我的工作方式是用大模型创意思考、加速比对,自己负责架构和最终正确性。这种协作让我在复杂的多线程系统中少走了许多弯路。


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

体验

  • :多线程编程一下子带来了竞态、死锁、活锁等问题,开始很不适应。
  • 迭代压力:三次作业在功能上层层叠加,架构需要前瞻设计,否则每次都要重构,非常痛苦。

建议

  1. 课程组可以提前给出一些多线程调试的技巧,比如 jstack 的使用、日志分析思路,帮助同学们起步。
  2. 指导书中的状态机描述非常详细,但若能提供一个轻量级的参考架构图(,会降低架构设计的难度。
  3. 互测数据限制,希望能提供更多边界样例,以暴露隐藏 bug,而不是让我们只在强测时才发现问题。

本单元的经历让我真正体会到并发程序的魅力与挑战,这段努力调试的回忆将深刻影响我未来的工程实践。

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

307

社区成员

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

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