面向对象设计与构造第二单元总结

刘隽如-24373185 2026-04-29 17:09:51

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

在本次的三次多线程作业中,我主要采用了经典的生产者-消费者模型来实现各个线程间的数据交互。为了保证数据的安全性,必须要谨慎地设置同步块和锁。

在我的设计中,主要的共享资源是全局的总请求队列 RequestQueue 和每部电梯专属的 ElevatorRequestPool

  • 锁的选择:我大量使用了对象锁(this关键字),通过给类内部的方法加上 synchronized 关键字来实现互斥。比如在 RequestQueue 中,offerpoll 方法都被声明为同步方法。
  • 同步块与处理语句的关系:在执行业务逻辑的线程(如 ElevatorThreadDispatcherThread)中,我尽量缩减了同步块(临界区)的范围。例如在电梯运行的主循环中,我只在检查电梯是否需要停止、以及是否需要等待新任务时,使用 synchronized (requestPool) 包裹 wait() 逻辑。而在电梯开门、移动等耗时且不需要锁的操作(如 Thread.sleep())时,坚决不占有锁,这有效避免了其他线程饿死或产生死锁的情况发生。

二、 调度器设计与调度策略演进

1. 调度器交互设计

调度器在我的架构中被抽象为了一个独立的 DispatcherThread 线程。

  • 它从全局的 RequestQueue 中取出(消费)用户的乘梯请求或系统发出的改造、检修等控制请求。
  • 它再将处理后的请求分发(生产)到各个电梯专属的 ElevatorRequestPool 中,随后对应电梯的 ElevatorThread 会被唤醒并执行具体任务。

2. 调度策略与性能适应

  • 第一次作业:按照题目要求,输入请求本身就指定了由哪部电梯负责。因此调度器只需要做无脑的分发,不需要考虑复杂的分配策略。
  • 第二、三次作业:系统不再为乘客指定电梯,需要我们自己进行分配,并且引入了双轿厢改造和临时检修。为此我设计了 ElevatorSelector 类来实现基于“代价函数(Cost Function)”的分配策略。
    • 在为乘客分配电梯时,我会综合计算每部可用电梯的 Cost:首先计算电梯当前楼层与乘客出发楼层的绝对距离代价;其次考虑方向,如果电梯正在向乘客靠拢则给予分数奖励,如果是背道而驰则给予巨大惩罚;同时也会把电梯当前的人数(载重)纳入惩罚考量。
    • 考虑到程序的运行时间(性能分),这种策略能够以贪心的方式让乘客最快上车;考虑到电量损耗,方向判断能尽量减少电梯无意义的折返跑。此外,选择器会刻意避开正在进行 update(改造)或 recycle(回收)任务的电梯,保证系统稳定性。

三、 多线程Bug与Debug方法总结

1. 出现过的Bug

  • 死锁:在第三次作业双轿厢运行逻辑中,为了防止同一井道内的主轿厢(运行范围F2-F7)和备用轿厢(运行范围B4-F2)在换乘层F2相撞,我在 Shaft 类中加入了位置判断。前期由于 Shaft 的锁与各自 ElevatorRequestPool 的锁存在交叉申请的情况,导致过严重的相互等待死锁。
  • 轮询:在使用 wait() 时,因为判断条件没有写好,导致线程被异常唤醒后没有进入阻塞状态,疯狂消耗 CPU 资源。

2. 个人Debug方法

  • 分析测评报错信息:在强测或互测报错时,仔细观察测评机给出的报错类型和错误发生的时间戳。例如遇到CTLE,我会重点检查各个线程里的 while 循环条件是不是写反了导致死循环;如果运行结果是未输出结束标记,则顺着时间线倒推哪一步卡住了。
  • 借助大模型辅助排查:当代码逻辑看起来没问题但依然死锁时,我会把涉及共享资源类的代码喂给大模型,让它帮忙检查潜在的锁交叉申请风险。虽然它不一定能直接给出完全正确的修改方案,但它的分析往往能为我提供排查的灵感和切入点。

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

经过本单元的锤炼,我最大的体会是:良好的层次化设计是保证线程安全的最好方法。

如果把所有的处理逻辑和状态改变都揉在一个类里,加锁就会变得像一团乱麻。在我的代码中,我按照“输入层 -> 调度层 -> 电梯执行层”进行了分层处理。

共享数据(各种队列)被提取出来独立成类,并在这些数据类内部封装好所有线程安全的 public synchronized 方法。这样一来,业务逻辑层(调度器和电梯本身)根本不需要关心底层是如何加锁、释放锁的,只需要无脑调用安全接口即可。这种“让数据保护自己”的层次化设计,极大地降低了线程安全问题的发生概率。

五、 大模型使用心得

  • 大模型的分工与优势:面对复杂的电梯任务,大模型在构建基础的类结构和状态机框架上效率很高。比如对于题目中繁杂的检修流程和双轿厢改造流程 ,大模型可以迅速帮我梳理出包含 NORMAL、UPDATE、DOUBLE 等状态的 switch-case 基础骨架,极大地减少了前期敲写模板代码的繁琐工作。
  • 遇到的困难与感受:大模型的短板在于复杂业务逻辑和严谨的并发控制。我发现它的算法设计能力相对有限,如果过度依赖它生成的调度策略,往往性能表现较差;而且在处理多把锁的依赖关系时,它给出的代码极易引发死锁。对于 wait()notifyAll() 的精准控制,大模型也常常会漏掉关键的边界条件。
  • 因此,我的体会是:大模型可以用来搭建基础框架、处理没有状态交互的纯逻辑代码,但涉及多线程安全和核心性能的算法设计,必须由开发者自己来构思和严格把控。

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

体验与感受: 从单线程过渡到多线程,思维方式的转变确实带来了一定的挑战。初期由于对 synchronizedwait/notify 机制的应用不够熟练,遇到了一些诸如死锁或线程无法正常结束的问题。但随着不断修改和查阅资料,理清了共享数据和锁的依赖关系后,看着自己设计的调度器能够有条不紊地指挥多部主副轿厢完成各种接送、改造和检修任务,整体的系统也按照预期顺利运转起来,还是非常有收获感和成就感的。

建议: 考虑到同学们在刚接触并发编程时,遇到死锁或者 CTLE 等问题往往容易无从下手,建议课程组可以在第二单元开始时,提供一份简易的“Debug指南”或“常见并发踩坑手册”。比如分享一些通过控制台日志排查死锁的实用技巧,或者简单介绍一下分析多线程报错的常规思路。这样可以在前期帮助大家减少毫无头绪的试错时间,把更多精力放在系统架构的思考上。

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

309

社区成员

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

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