2026-OO-U2-Summary

24373369石若霖 2026-04-29 23:19:55

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

在不同阶段,针对不同的共享资源选择了不同的锁对象:

  • 队列锁(WaitQueue, Queue):这是最基础的生产者-消费者模型锁。InputThread 作为生产者,电梯或调度器作为消费者,通过锁住队列对象实现请求的存取。
  • 容器锁(ElevatorThread):在 DispatchThread 中,为了应对电梯的动态增加(如升级为双轿厢)和状态巡检,我锁定了电梯列表容器,防止在遍历过程中出现并发修改异常。
  • 物理空间锁(Shaft):这是第三次作业的难点。为了解决双轿厢在换乘层的碰撞问题,我抽象出了井道对象,将锁细化到具体的物理空间,实现电梯间的空间互斥。

同步块的设置遵循了最小化锁持有时间的原则:

  • 方法级同步:在简单的线程安全类(如 Queue)中,直接使用 synchronized 修饰方法,确保原子性的读写。
  • 块级同步:在复杂的业务逻辑(如 openAndClose)中,仅在涉及乘客上车、从队列中删除请求的关键时刻才进入同步块,而在 Thread.sleep 模拟耗时时主动释放锁。

二、调度器设计分析

1.评分系统设计

没有写太过复杂的调度器(如计算时间、影子电梯等等),只做了一个简单的评分,为每台电梯计算一个分值,分值越低表示该电梯承接该任务的代价越小:

  • 基础分:基于 abs(personFloor - elevatorFloor) * 5,即物理距离越近,分值越低。

  • 方向加成:如果电梯运行方向与乘客目标一致且未经过乘客,则视为顺风车,分值最低;如果方向相反,则大幅增加惩罚分(+1000),因为这通常意味着乘客需要等待电梯折返。

  • 空闲激励:空闲电梯(direction == 0)会获得较小的基础惩罚分(+100),鼓励系统利用闲置资源。

  • 虚拟重量:计算分值时,我不只看电梯内的实时重量,还通过 VirtualWeight() 加上了已经分配给该电梯但尚未上车的乘客重量。

  • 超重保护:如果“实时重量 + 虚拟重量 > 400”,该电梯分值直接飙升(+10000)。这避免了多名乘客蜂拥至同一台电梯导致频繁由于超重而开门失败,从而减少了不必要的电量损耗(开门最耗电)和时间浪费。

  • 楼层可达:在第三次作业中,增加了对楼层可达性的强约束判断,通过 getMinFloor() 和 getMaxFloor() 过滤掉无法到达乘客起始层的电梯。

2.调度器与线程的交互

采用了分级交互,调度器作为中间层,通过两个关键队列与不同线程协作:

  • 与输入线程 (InputThread) 交互:调度器作为消费者,通过 waitQueue.takeRequest() 阻塞式地获取任务
  • 与电梯线程 (ElevatorThread) 交互:调度器作为生产者,通过 chooseElevator 算法选定目标电梯,并调用其私有队列的 addRequest 方法。在第三次作业中,交互变得更加紧密。电梯在完成维修(finishMaint)或升级(update)后,会主动回调调度器的方法,更新调度器的状态表(status[])。

三、bug分析

在三次作业中, 第二次被发现的bug最多。

  • 主线程过早退出:在本地测试环境下程序正常,但在评测机环境下,输入维修指令后,系统仅输出部分结果便结束线程。引入 isAllFinished 函数。该函数不仅检查 WaitQueue 是否为空,还必须扫描所有电梯的内部状态(轿厢内是否有乘客、私有队列是否为空、是否处于非 NORMAL 状态)。主线程必须在所有电梯真正进入静默后才能退出,确保了生命周期的完整性。
  • 维修状态下的非法分配:当所有电梯都在维修时,调度器依然会强行分配乘客,导致乘客被派发到了一个无法工作的电梯上。其实就是调度器的决策逻辑与电梯状态产生了脱节。在执行 chooseElevator 时,缺乏对电梯当前状态的过滤。在 chooseElevator 中增加状态校验。当电梯状态非 NORMAL 时直接跳过。同时,在 DispatchThread 的主循环中增加二次等待:如果 chooseElevator 返回 0(无可用电梯),调度器应进入 wait状态,直到有电梯完成维修或升级并发出 notifyAll 信号。

主要还是通过分析输出的日志dbug,析日志中 RECEIVEINOUTARRIVE 的出现时间点,重构每个请求的生命周期,从而定位是在哪个环节出现了逻辑断裂(例如:人还没出电梯,电梯就开始维修了)。在关键节点(获取锁、释放锁、进入 wait、发送通知)打印带有时间戳和线程 ID 的日志。

四、线程安全和层次化设计

在作业中,电梯的状态(如维修、升级、正常)和它持有的资源(乘客、私有队列)必须保持原子性。当一个电梯进入维修状态(REP_ACCEPT)时,它清空队列并将乘客重新投入总等候队列。如果这组操作不是原子的,调度器可能在电梯还没清空队列时又给它分配了新乘客,导致请求丢失。在三次作业中,通过将状态与队列操作封装在同一个锁作用域内,确保了电梯在转换角色的瞬间,其资源状态对外界是逻辑自洽的。

线程安全不仅是防止“数据竞争”,更要处理过早唤醒和信号丢失,wait() 必须放在 while 循环中。例如调度器在没发现可用电梯时进入 wait,唤醒它的可能是某部电梯修好了,也可能是输入结束了。通过严密的条件检查,确保线程被唤醒后必须重新评估全局状态,避免了在不具备执行条件下盲目运行导致的非法分配。

五、大模型的使用

在本次多线程的三次作业中,对于多线程的debug问题,ai还是比较擅长的,虽然ai很难找出某一条信息错误的点,但是能很好的找出线程之间存在的交互问题,指出潜在的竞态和死锁,提供排查思路。

六、体验和感受

终于度过了电梯月,纵观三次作业,感觉第一次作业还是比较简单的,毕竟指定了电梯,第二次作业最难,尤其是加上了维修状态,当时爆出了好多bug,熬到很晚才de完。。在多线程的世界里,仅仅是前后语句的顺序的改变就可能产生不同的结果和状态。

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

304

社区成员

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

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