面向对象第二次博客作业

王瀚琦-24371459 2026-05-05 21:59:27

不知为何五一前没交上去,所以再交一遍。

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

在本单元的设计中,我采用了经典的生产者-消费者模型,并将“托盘”作为一个独立的线程安全类进行加锁。

  1. 锁的选择
    • 核心锁对象是队列(如全局等待队列 globalQueue 和各个电梯的私有处理队列 processQueue)。
    • 在选择锁时,我遵循了最小化临界区的原则,避免将锁加在整个读写方法上,而是使用 synchronized (queue) 保护核心的增删查改逻辑。
  2. 同步块与处理语句的关系
    • 同步块内部坚决不执行耗时操作(如 Thread.sleep 或网络/复杂计算)。例如,ShadowSimulator 对未来代价进行模拟计算时,属于耗时甚至迭代的逻辑,因此我们在同步块中只做一件事:深克隆(deepClone)当前状态。随后在同步块外部基于克隆副本进行耗时操作。
    • 使用了标准的 wait() - notifyAll() 规范。在 InputTask 放入新请求后,仅对共享队列执行 notifyAll(),唤醒 DispatcherTask 使得同步块的职责非常纯粹:控制数据的可见性和原子性。

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

1. 调度器设计与线程交互

我的调度器采用了独立的 DispatcherTask 线程。它充当了系统中的“中枢神经”:

  • 上游交互:从 InputTask 填充的 globalQueue 中提取请求(阻塞式 wait 直到有新请求)。
  • 下游交互:将请求分配给具体的 ElevatorTask 对应的 processQueue 中。并且它还负责拆分和解析多段路径(如跨越 F2 换乘层的乘客),以及分发维护(MaintRequest)和双轿厢改造(UpdateRequest)请求。

2. 调度策略与多性能指标适应

在此次作业中,由于需要兼顾时间电量,我放弃了简单的分配原则,而是实现了影子模拟器(ShadowSimulator)

  • 策略实现:结合了 LookStrategy 的基础运行逻辑,我为电梯设计了模拟器。当分配一个乘客时,调度器会利用 deepClone 复制所有电梯副本,然后分别“试运行”接下这个乘客的情形。
  • 性能平衡:模拟器不仅计算运行时间,也会累计耗电量。我通过自定义的公式 $\Delta \text{cost} = \alpha \times \text{Time} + \beta \times \text{Power}$ 来计算边际代价。最终,调度器总是将乘客分配给 $\Delta \text{cost}$ 最小的电梯,这一套机制极其健壮地适应了后续多项性能指标的要求。

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

1. 典型Bug分析

在架构迭代的过程中,我也踩了不少坑,最具代表性的是以下两个多线程/深拷贝带来的问题:

  • 深克隆(deepClone)遗漏状态导致模拟器失效
    在引入换乘机制时,我为 Passenger 增加了 transToIdx 用于记录多段行程的目的地。但在 PassengerdeepClone 构造函数中,忘记了拷贝该属性。这导致 ShadowSimulator 在模拟时所有乘客的目标都退化为初始默认值(例如B4层),进而使得所有分配方案的 $\Delta \text{cost}$ 固定(如固定的4400ms)。这反映出即使不涉及线程冲突,对象快照的不一致也会让高级策略瞬间崩溃
  • UPDATE 状态的双轿厢转换 RTLE(超时)
    双轿厢改造要求极其严格的时序(NORMAL -> UP_ACCEPT -> UPDATE -> DOUBLE 以及 GHOST 线程的启动逻辑)。在 ElevatorTask 处理相关状态机时,因为 MAINMINOR 的共享换乘层(F2)未正确处理避让和互斥,使得轿厢进入了互相等待或者频繁 sleep() 的死胡同,最终导致 RTLE。

    2. Debug 方法指南

    多线程环境下的Bug有着极强的不确定性,我的 Debug 经验总结如下:
  • 日志隔离法:使用统一配置的 System.out.println(System.currentTimeMillis() + Thread.currentThread().getName() + " " + State) 打印状态转移,并通过不同标识符将 DispatcherElevatorTask 的日志区分。
  • 快照追踪法:当逻辑不对时,不直接看庞大的对象,而是检查参与任务的输入和输出快照。
  • 降维打击:出现 RTLE 时,把不相关的电梯线程关掉(通过自定义输入参数),只开一部电梯模拟双轿厢 UPDATE 流程,更容易捕捉死锁和逻辑循环。

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

经历三次作业的洗礼,我对多线程架构和对象层次划分有了更深的理解:

  • 线程安全的本质是“控制共享”:我们不应该到处加锁,而是要让共享状态尽量收敛。在本程序中,数据类(如 Passenger, Elevator 属性)作为被动数据,由保护它们的“托盘”(如 RequestQueue)来集中做同步。工作线程不再彼此调用方法,它们只向托盘存取数据。这种解耦让线程安全更容易证明。
  • 层次化让系统从容应对需求
    在架构早期,我就分清楚了三层:
    1. 输入层(InputTask):解析源源不断的数据。
    2. 调度/策略层(DispatcherTask & Strategy):负责分配逻辑(如 Shadow 模拟和双轿厢路径)。
    3. 执行层(ElevatorTask):只负责机械的 openclosemove 状态机。
    正是因为执行层不知道复杂的策略,策略层不需要关心怎么开门,当第三次作业加入“双轿厢”和“定制化策略”时,我只需要在调度层维护换乘逻辑,在执行层加一个 UPDATE 状态机即可,做到了真正的“高内聚低耦合”。

总结来说,多线程如同指挥一个乐团,线程安全是演奏不走调的底线,而优秀的层次设计和高度解耦则是演奏出华丽篇章(高分性能)的必要前提。

五、大模型的使用心得

  • 对于大模型的使用,本人认为其用在最开始对整体架构设计的参考讨论还是很好的,能快速准确地规划出合理的设计路线。
  • 另外大模型在自己写好后进行简单的检查也能起到安心的作用,保证每一步没有低级错误,更稳定地推动程序设计过程。

六、第二单元感受

  • 总的来说,多线程是本人曾经没有接触过的部分,所以前面几次作业确实有点吃力。
  • 个人认为本单元第二次作业中既要设计分发器的影子电梯核心逻辑,并且还要维护维修请求,对当次作业的负担还是有些大的。
  • 不过总体上还是深入理解了多线程的设计思想和逻辑,学到了许多。
...全文
17 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

304

社区成员

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

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