307
社区成员
发帖
与我相关
我的任务
分享在多线程电梯系统中,同步控制是正确性的基石。我的同步设计经历了从粗糙到精细的迭代。
1. 锁的选择
我主要使用了两种锁机制:
RequestQueue、RequestTable 等共享队列,保护 put、take、retrieve 等临界操作。之所以选择它,是因为这些数据结构被频繁访问,synchronized 语法简洁,且通过 wait/notifyAll 很容易实现阻塞等待。ElevatorPair 中的双轿厢碰撞检测。这里需要手动控制锁,因为碰撞检查涉及多个共享变量primaryFloor、secondaryFloor 等,ReentrantLock 的 lock()/unlock() 可以精确控制范围,并且可以创建条件变量,比 synchronized 灵活。2. 同步块与操作的关系
RequestTable 中的所有公共方法都是 synchronized,保证了等待队列的一致性。例如 retrieveSameDirection 必须在取出乘客时原子地判断方向和移除元素,防止两个电梯线程同时接走同一乘客。ElevatorThread 中的 onboard 列表通过 synchronized (onboard) { ... } 锁保护,保证对轿厢内乘客的修改上下客是线程安全的。特别在 handleDropOff 和 handlePickUp 中,对 onboard 的遍历和修改必须加锁,否则会导致并发修改异常或重量计算错误。hasMovePermission 中虽然没有直接加锁,但它读取了 onboard.isEmpty() 和 table.isEmpty() 等需要同步的信息。我的做法是让这些 getter 方法自身同步,从而间接保证可见性和原子性。3. 从历史修复看同步缺陷
早期版本中,onboard 的修改没有完整加锁,导致在 handleDropOff 中可能发生并发删除。后来我统一对 onboard 的迭代器操作加锁,问题才消失。这印证了:只要涉及共享可变集合的遍历,必须同步。
1. 调度器的角色与线程交互
调度器 Scheduler 本身是一个线程,居中协调:
InputThread 将乘客/检修/改造/回收请求放入 inputQueue。inputQueue 或 globalQueue中转重分配乘客中取出请求。chooseElevator(p) 选择电梯后,输出 RECEIVE 并将 Person 放入对应电梯的 RequestTable。MAINT、UPDATE、RECYCLE,直接调用电梯的 acceptMaint/acceptUpdate/acceptRecycle 方法触发状态转换。交互的关键在于 wait/notify:
table.isEmpty() && !end 时会调用 table.wait() 进入等待,调度器 addPerson 后调用 table.notifyAll() 唤醒。wait 自身,等待电梯完成特殊流程(如维修结束)后通知 Scheduler.class.notifyAll() 唤醒。2. 调度策略与性能适应
我的调度策略采用“最小负载优先”:遍历所有可用电梯,计算“轿厢内人数 + 等待队列长度”作为负载,选择负载最小的电梯。这个策略简单但有效,能够自然平衡系统负载,缩短平均完成时间。
在适应性能指标方面:
WAIT 避免空跑,减少无效移动。Strategy 中增加了“双轿厢模式下 F2 无任务必须立刻离开”的逻辑,减少阻塞等待的电量浪费。不过我的策略仍有提升空间,例如没有显式预测电梯到达时间,只是简单比较负载,但整体性能已经能满足公测和互测要求。
本次作业中我遇到了两个棘手的多线程 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
ElevatorPair.canMoveTo() 中,备用轿厢的交叉换位逻辑写反。主轿厢分支判断交叉时 return true允许,备用轿厢对称处却是 return false,导致备用轿厢被错误阻挡在 F2,随后主轿厢到达产生碰撞。return false 改为 return true,恢复对称性。Debug 方法:
层次化设计是将系统分解为独立的逻辑层,每层只关心自己的职责,通过明确接口协作。我的电梯系统分为:
InputThread:只负责解析输入,放入队列。Scheduler:负责分配乘客、处理特殊请求。ElevatorThread:负责电梯运行、状态转换。Strategy:根据电梯状态和请求表给出行动建议。ElevatorMover:执行具体的移动、开关门、上下客。ElevatorPair:管理双轿厢碰撞和换乘。这种分层让各层职责清晰,线程安全可以局部化。例如,所有对 onboard 的修改集中在 ElevatorMover 中,通过锁保护;调度器只修改 RequestTable,电梯只读取它。层次化也使得 bug 修复可以精准定位到某一层,比如碰撞问题只需修改 ElevatorPair 的判断逻辑,而不影响其他部分。
线程安全的核心体会:
模型名称:DeepSeek
分工模式:
优势:
困难:
canMove 中加检查,但没有抓住“sleep 前修改变量”的根本问题,后来还是靠反复对话才找到真正原因。感受:
大模型是一个有力的伙伴,但它不能替代自己对代码的彻底理解。我的工作方式是用大模型创意思考、加速比对,自己负责架构和最终正确性。这种协作让我在复杂的多线程系统中少走了许多弯路。
体验:
建议:
本单元的经历让我真正体会到并发程序的魅力与挑战,这段努力调试的回忆将深刻影响我未来的工程实践。