OO Unit2博客

郭祐祺-24373248 2026-04-29 23:17:01

2026OO Unit2

1. 三次作业演进总览

1.1 需求演进

第五次作业是第二单元多线程电梯的起点。输入中每个乘客请求已经指定 BY 电梯号,因此调度器的职责相对简单:输入线程读到请求后封装成事件,分派线程根据请求中指定的电梯编号,把任务转发到对应电梯的请求表。此时主要关注点是基础线程协作、RECEIVE 输出时机、电梯等待与终止。

第六次作业取消了指定电梯,乘客请求需要由程序自行分配。同时加入 MAINT 临时检修请求,检修会改变电梯生命周期:电梯从普通服务状态进入接收检修、维修、测试,期间已有乘客可能 OUT-F 下车并重新调度,等待队列中的任务也可能被撤销 RECEIVE 后重新进入全局调度。因此 hw6 的核心难点从“线程安全转发”变成“线程安全调度 + 状态机 + 重新分配”。

第七次作业进一步加入 UPDATERECYCLE 和双轿厢。一个井道内出现主轿厢和备用轿厢两个执行线程,且二者共享井道状态、共享物理空间约束,必须避免同层冲突和穿越冲突。最终代码中,原本集中在 ElevatorThread 内部的状态逐渐被拆出为 CabinStateShaftContextDoubleCarRuntimeControlPhaseRuntime,这是架构演进中最明显的一步。

1.2 代码结构演进

hw5 的主要结构如下:

  • InputThread:读取 PersonRequest,写入 DispatcherInbox
  • DispatcherInbox:输入线程和调度线程之间的共享事件队列。
  • DispatchThread:从事件队列取请求,按指定电梯号投递。
  • DispatchStrategy:直接返回 task.getElevatorId()
  • ElevatorRequestTable:每部电梯的等待请求表。
  • ElevatorThread:电梯执行线程,负责移动、开关门、上下客。
  • ElevatorStrategy:决定 Wait / Open / Up / Down
  • PersonTask:乘客任务封装,维护 TaskStage

hw6 在 hw5 基础上增加:

  • pendingTasks:调度器维护的待分配任务列表。
  • ElevatorDispatchSnapshot:电梯对调度器暴露的快照。
  • ServiceStateNormal / Rep_Accept / Repair / Test
  • MaintJobMaintRequestEvent:检修任务封装。
  • RedispatchPersonEvent:乘客重新调度事件。
  • ElevatorUpdateEvent:电梯状态变化后唤醒调度器重新尝试分配。

hw7 在 hw6 基础上增加:

  • ShaftContext:统一管理井道状态、主副轿厢、预约楼层和控制任务。
  • ShaftStateNormal / Rep_Accept / Repair / Test / Up_Accept / Update / Double / Rec_Accept / Recycle
  • CabinState:单个轿厢的楼层、方向、门、载客状态。
  • DoubleCarRuntime:双轿厢运行逻辑。
  • ControlPhaseRuntime:UPDATE / RECYCLE 的接收期、改造期、回收期逻辑。
  • UpdateRequestEventRecycleRequestEvent:控制请求事件。

2. 同步块设置与锁选择分析

2.1 共享资源总表

共享资源被哪些线程访问生产数据消费数据
DispatcherInbox.queueInputThreadDispatchThread、各 ElevatorThread输入线程加入外部请求;电梯线程加入 ElevatorUpdateEvent / RedispatchPersonEvent调度线程 getEvent()
DispatcherInbox.inputEnded输入线程、调度线程输入线程在读到 null 后置位调度线程终止判断
ElevatorRequestTable.waitingTasks调度线程、电梯线程、控制期 runtime调度线程 receiveTask() 加入;部分控制期可能重新入队电梯线程 pollBoardableAtFloor() 消费
ElevatorRequestTable.dispatcherClose调度线程、电梯线程调度线程关闭请求表电梯线程等待和终止判断
ElevatorRequestTable.forceWakeUp电梯线程、控制请求处理wakeUp() 设置awaitIfIdle() 消费并清除
CabinState.currentFloor / direction / doorState / passengerCount所属电梯线程写,调度线程读快照电梯线程运行时更新调度策略读 ElevatorDispatchSnapshot
CabinState.passengers所属电梯线程为主电梯线程上客加入电梯线程下客移除,策略构造上下文时复制
ShaftContext.state同井道主/副电梯线程、调度线程调度线程接收控制请求,电梯线程推进状态电梯线程选择运行分支,调度器判断可否分配
ShaftContext.mainFloor/spareFloor/reservedFloor主副轿厢线程电梯移动前预约,抵达后释放awaitMovePermit() 判断是否可进入下一层
PersonTask.stage/fromFloor/activeElevatorId调度器、电梯线程receiveTask()board()transferAt() 等修改调度器和电梯策略读取

2.2 DispatcherInbox 的同步设计

DispatcherInbox 是输入层和调度层之间的生产者-消费者队列。关键代码包括:

public synchronized void addEvent(InputEvent event) {
    if (dispatcherClosed || event == null) {
        return;
    }
    queue.offer(event);
    notifyAll();
}

public synchronized InputEvent getEvent() {
    while (queue.isEmpty() && !dispatcherClosed) {
        wait();
    }
    if (queue.isEmpty()) {
        return null;
    }
    return queue.poll();
}

锁对象是 DispatcherInbox 自身。保护变量是 queueinputEndeddispatcherClosed。同步块内部操作包括入队、出队、结束标志设置和通知。这里必须加锁,因为 queue.isEmpty()wait()queue.poll() 共同构成一个条件等待过程,不能拆开。

我在 hw6/hw7 区分了 inputEndeddispatcherClosed。输入结束后调度器仍可能需要处理电梯重新调度事件,只有全系统空闲后才关闭调度器。

2.3 ElevatorRequestTable 的同步设计

每部电梯拥有一个 ElevatorRequestTable。调度器向其中写入任务,电梯线程从中查询、捎带和取走任务。

关键方法是 receiveTask()

public synchronized void receiveTask(PersonTask task, int elevatorId) {
    TimableOutput.println(String.format("RECEIVE-%d-%d", task.getId(), elevatorId));
    task.startReceive(elevatorId);
    waitingTasks.add(task);
    notifyAll();
}

这里锁对象是对应电梯的请求表。同步块保护 waitingTasksdispatcherClose/forceWakeUp。把 RECEIVE 输出、task.startReceive()waitingTasks.add() 放在同一个锁内,能够保证一个重要不变量:输出 RECEIVE-x-y 后,这个任务已经进入 y 号电梯请求表,且其 TaskStage 已经从 New 变为 Waiting

awaitIfIdle() 是第二个重点:

public synchronized void awaitIfIdle(boolean empty) {
    while (waitingTasks.isEmpty() && empty && !dispatcherClose && !forceWakeUp) {
        wait();
    }
    forceWakeUp = false;
}

这里的等待条件和 receiveTask()/setDispatcherClose()/wakeUp() 的通知条件共享同一把锁,避免“检查为空后刚要睡眠,调度器已经入队并通知”的漏唤醒问题。

2.4 电梯开关门阶段的同步设计

hw5、hw6、hw7 中,电梯开门逻辑都避免在 sleep(openTime) 时持有请求表锁。以 hw7 普通运行的 openAndServe() 为例:

cabinState.setDoorState(DoorState.Open);
TimableOutput.println("OPEN...");
unloadPassengers();
boardPassengers();

sleepMillis(config.getOpenTime());
unloadPassengers();
synchronized (requestTable) {
    boardPassengers();
    TimableOutput.println("CLOSE...");
    cabinState.setDoorState(DoorState.Closed);
}

这里同步块只包住关门前的最后一次上客和关门状态修改。目的是为了在关门前,如果调度器刚好投递了可在当前楼层上车的请求,电梯有机会在同一个临界区内完成最后一次检查和上客,减少 RECEIVECLOSE 的边界错误。

2.5 ShaftContext 的同步设计

hw7 的 ShaftContext 是最关键的新锁对象。它保护的是同一个井道内主、副轿厢共享的不变量:

  • 井道当前状态 ShaftState
  • 主轿厢与副轿厢当前楼层;
  • 主/副轿厢预约的下一楼层;
  • F2 换乘层等待标记;
  • 是否允许备用轿厢参与调度;
  • 控制请求接收时间。

状态转换方法如 acceptUpdateJob()finishUpdateWithOutput()acceptRecycleJob()finishRecycleWithOutput()acceptMaintJob() 等均为 synchronized。每次状态变化后都 notifyAll() 并调用相关电梯的 wakeForControl()

双轿厢移动许可的核心在 awaitMovePermit()

public synchronized void awaitMovePermit(boolean isMainElevator, int nextFloor) {
    while (!canEnterNextFloor(isMainElevator, nextFloor)) {
        wait();
    }
    if (isMainElevator) {
        mainReservedFloor = nextFloor;
    } else {
        spareReservedFloor = nextFloor;
    }
}

它将“能否进入下一层”的检查和“预约下一层”的修改放在同一把锁内,避免两个轿厢同时判断安全、随后同时进入冲突楼层。onArrive() 抵达后清除预约并通知等待线程。

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

3.1 调度器职责分析

hw5 中,DispatchThread 的职责是:

  • DispatcherInbox 取输入事件;
  • 将乘客请求转为 PersonTask
  • 根据 DispatchStrategy 选择电梯;
  • 调用对应 ElevatorRequestTable.receiveTask()
  • 输入结束后关闭所有请求表。

hw6/hw7 中,调度器职责明显扩张:

  • 维护 pendingTasks
  • 根据所有电梯快照选择目标电梯;
  • 暂存暂时无法分配的任务;
  • 处理 RedispatchPersonEvent
  • 处理 MAINT / UPDATE / RECYCLE;
  • 接收 ElevatorUpdateEvent 作为重新尝试分配的触发;
  • 判断全系统是否可以终止。

3.2 调度器与各线程的交互

InputThread -> DispatchThread

InputThread 不直接调用调度器方法,而是向 DispatcherInbox 写事件。这样输入和调度解耦,输入线程只负责解析官方包请求:

  • PersonRequest -> PersonRequestEvent
  • MaintRequest -> MaintRequestEvent
  • UpdateRequest -> UpdateRequestEvent
  • RecycleRequest -> RecycleRequestEvent

DispatchThread -> ElevatorThread

调度器通常不直接操作电梯内部状态,而是:

  • 普通乘客:写入对应 ElevatorRequestTable
  • MAINT:hw6 直接调用 elevator.acceptMaintenanceJob(),hw7 改为调用 shaftContext.acceptMaintJob()
  • UPDATE/RECYCLE:调用 ShaftContext 状态转换入口。

ElevatorThread -> DispatchThread

电梯线程通过 DispatcherInbox.addEvent() 反向通知调度器:

  • 空闲或状态变化时发送 ElevatorUpdateEvent
  • 检修、改造、双轿厢换乘时发送 RedispatchPersonEvent

这种做法让调度器不需要轮询等待所有电梯状态,而是被状态变化事件唤醒。

MainThread -> 其他线程

Main 负责创建共享对象和线程,启动顺序为:

  1. 创建 DispatcherInbox
  2. 创建所有电梯的 ElevatorRequestTableElevatorThread
  3. 创建 DispatchThread
  4. 先启动调度线程,再启动电梯线程,最后启动输入线程;
  5. 依次 join() 输入线程、调度线程和电梯线程。

3.3 普通乘客请求时序图

sequenceDiagram
    participant I as InputThread
    participant Q as DispatcherInbox
    participant D as DispatchThread
    participant S as DispatchStrategy
    participant T as ElevatorRequestTable
    participant E as ElevatorThread

    I->>Q: addEvent(PersonRequestEvent)
    D->>Q: getEvent()
    Q-->>D: PersonRequestEvent
    D->>D: pendingTasks.add(PersonTask)
    D->>S: chooseElevator(task, elevatorSnapshots)
    S-->>D: elevatorId
    D->>T: receiveTask(task, elevatorId)
    T-->>E: notifyAll()
    E->>T: pollBoardableAtFloor()
    E->>E: OPEN / IN / CLOSE / ARRIVE / OUT
    E->>Q: addEvent(ElevatorUpdateEvent)

3.4 MAINT 请求流转

hw6 中 MAINT 流程为:

  1. InputThread 读到 MaintRequest,封装为 MaintRequestEvent
  2. DispatchThread 调用目标电梯 acceptMaintenanceJob()
  3. 电梯状态从 Normal 进入 Rep_Accept,请求表 wakeUp()
  4. 电梯在限制时间内向 F1 行驶,途中可进行部分服务。
  5. 到 F1 后输出维修工 IN,输出 MAINT1-BEGIN,等待请求表中的任务被 cancelReceive() 后重新投递。
  6. 进入 Repair,等待 1s 后输出 MAINT2-BEGIN
  7. 测试到目标楼层再回 F1,维修工 OUT-S,输出 MAINT-END
  8. 状态回到 Normal,通知调度器。

hw7 中 MAINT 状态进入 ShaftContextElevatorThread 不再独自维护完整服务状态,而是通过 shaftContext.beginRepairWithOutput()beginTestWithOutput()finishMaintWithOutput() 推进状态。

3.5 UPDATE 请求流转

  1. InputThreadUpdateRequest 封装为 UpdateRequestEvent
  2. DispatchThread 调用对应井道 acceptUpdateJob()
  3. ShaftContextNormal 进入 Up_Accept,关闭备用轿厢调度,唤醒主轿厢。
  4. 主轿厢由 ControlPhaseRuntime.runUpAcceptStep() 驶向 F3,途中在时间允许时可释放或接纳部分乘客。
  5. 到达准备楼层后输出 UPDATE-BEGIN,进入 Update
  6. 静默等待改造时间后输出 UPDATE-END
  7. ShaftContext 进入 Double,启用备用轿厢,唤醒主/副轿厢。

3.6 RECYCLE 请求流转

  1. InputThreadRecycleRequest 封装为 RecycleRequestEvent
  2. DispatchThread 调用 ShaftContext.acceptRecycleJob()
  3. 井道从 Double 进入 Rec_Accept,停止备用轿厢继续接受新任务。
  4. 备用轿厢由 ControlPhaseRuntime.runRecycleAcceptStep() 驶向 F1 准备回收。
  5. 到达后输出 RECYCLE-BEGIN,进入 Recycle
  6. 静默等待后输出 RECYCLE-END
  7. ShaftContext 回到 Normal,备用轿厢不再参与调度。

4. 调度策略与时间/电量性能指标

4.1 外层调度策略

hw5 的外层调度策略是指定电梯策略,DispatchStrategy.chooseElevator() 直接返回 task.getElevatorId()

hw6/hw7 的外层调度策略是基于评分函数的贪心策略。DispatchStrategy.computeScore() 主要考虑:

  • 当前电梯到乘客出发楼层距离;
  • 电梯等待队列长度;
  • 电梯当前载客数;
  • 本轮调度中已经虚拟分配给该电梯的额外任务数;
  • 电梯当前方向是否会经过乘客出发楼层;
  • 电梯方向是否与乘客方向一致;
  • 电梯是否刚好在乘客出发楼层。

权重设置为:

  • 距离乘以 10;
  • 等待数乘以 8;
  • 载客数乘以 6;
  • 虚拟额外负载乘以 12;
  • 顺路经过减分;
  • 同向进一步减分;
  • 反向或不顺路加分;
  • 同楼层且可顺手接人减分。

4.2 内层电梯运行策略

hw5 我的策略更接近主请求/首请求策略:没有乘客时看请求表第一个任务,有乘客时跟随乘客目标方向运行。

hw6 中优化了此策略,让我的最终策略更接近 LOOK:

  • 当前层有目标乘客下车,先开门;
  • 轿厢内有人时,尽量保持当前方向;
  • 当前方向上有乘客目的地或等待请求,则继续前进;
  • 当前方向无需求,再考虑反向;
  • 轿厢为空时,选择最近等待任务;
  • 当前层有等待任务时,按上下方向等待数量和前方需求决定方向。

hw7 中双轿厢阶段还增加了服务范围约束:

  • 主轿厢负责 F2 及以上区域;
  • 备用轿厢负责 F2 及以下区域;
  • 跨区乘客在 F2 OUT-F 后重新调度。

4.3 复杂场景分析

MAINT 场景中,电梯进入 Rep_Accept 后会逐步向 F1 靠拢,并在合适情况下释放乘客或继续接纳向 F1 方向的乘客。等待队列中的请求通过 drillAllWaitingTasks() 取出,cancelReceive() 后重新投递,避免检修电梯继续持有任务。

UPDATE 场景中,主轿厢到 F3 后进入改造,改造完成后备用轿厢启用。调度策略通过 ShaftState 限制可服务区间,避免将下半区任务分给未启用的备用轿厢。

RECYCLE 场景中,备用轿厢停止接收新任务,进入回收接收期和静默回收期。调度策略在 Rec_Accept/Recycling 状态下将可服务范围重新收敛。

双轿厢冲突由 ShaftContext.awaitMovePermit() 处理,它检查对方当前楼层、对方预约楼层,以及主副轿厢相对位置,保证不会进入同一楼层或互相穿越。

4.4 策略总体取舍

我的程序整体是“正确性优先,时间次之,能耗再次”的策略。优点是面对 MAINT、UPDATE、RECYCLE 和双轿厢时状态边界比较清楚,不容易为了局部性能破坏合法性。缺点是能耗没有特别考虑,双轿厢跨区采用 F2 重新调度,可能牺牲部分全局最优。

5. Bug 复盘与多线程 Debug 方法

在第六次作业的互测中出现了TLE的相关 bug。

  • Bug 现象:在多部电梯进入检修或恢复阶段时,调度器可能把大量待分配乘客都压到唯一可用电梯上,导致该电梯负载过高,造成超时。
  • 根因定位:dispatchPendingTasks() 原先只要某部电梯可调度,就持续向它分配;没有考虑其他电梯正在恢复,短时间后可重新承担任务。
  • 修复方式:DispatchThread 中加入 SOLE_DISPATCH_BACKLOG_LIMIT。如果存在恢复中电梯,且当前唯一可调度电梯已有负载达到阈值,就暂缓继续分配。

我在这个单元中采用的多线程 Debug 方法:

  1. 记录每次 wait/notify 的条件。
  2. 记录电梯状态机变化。hw6 关注 ServiceState,hw7 关注 ShaftState
  3. 构造极小复现样例。比如两个乘客加一个 MAINT,或一个 UPDATE 加一个跨 F2 请求。
  4. 对照输出合法性检查器定位问题。先看第一个非法输出点,再反推该乘客最近一次 RECEIVE/IN/OUT-F
  5. 扔给 AI (性价比最高)

6. 线程安全与层次化设计理解

6.1 线程安全理解

线程安全不是简单地“所有方法都加 synchronized”。如果锁对象选错,或者同步块没有覆盖完整不变量,程序仍然不安全。我的程序最终主要围绕三类共享资源设锁:

  • DispatcherInbox 保护输入事件队列;
  • ElevatorRequestTable 保护单部电梯的等待请求表;
  • ShaftContext 保护一个井道内的状态机和双轿厢空间约束。

锁保护的是不变量,而不是孤立变量。例如 ElevatorRequestTable 中真正要保护的是“等待队列状态、RECEIVE 生命周期、空闲等待条件”之间的一致性;ShaftContext 中真正要保护的是“主副轿厢不能同时占据或预约冲突楼层”。

条件判断和状态修改必须原子化。awaitMovePermit() 中,如果“判断能否进入下一层”和“预约下一层”之间没有锁,两个轿厢可能同时判断安全并预约同一层。receiveTask() 中,如果 RECEIVE 输出和入队分离,也可能出现输出已经发生但电梯还看不到任务的边界问题。

wait/notify 的本质是基于条件的协作。线程不是因为“有人通知”就一定能继续,而是因为醒来后重新检查条件成立才继续。因此所有等待都应该写成 while (条件不满足) wait()

6.2 层次化设计理解

三次作业可以分为几个层次:

  • 输入层:InputThread,只负责官方输入解析和事件封装。
  • 调度层:DispatchThreadDispatchStrategy,负责全局分配。
  • 共享资源层:DispatcherInboxElevatorRequestTable,负责线程间缓冲。
  • 执行层:ElevatorThread,负责实际移动、开关门、上下客。
  • 状态层:ShaftContextCabinStateMaintenanceContext,负责可见状态和状态机。
  • 策略层:ElevatorStrategy,负责单梯下一步动作选择。
  • 输出层:通过 TimableOutput 统一输出。

调度器不应该直接控制过多电梯内部细节。最终代码中,调度器只拿 ElevatorDispatchSnapshot,不直接修改电梯楼层、方向、门状态,这是较好的边界。

电梯线程不应该关心全局调度策略。电梯只从自己的 ElevatorRequestTable 中取任务,状态变化时发事件通知调度器;具体新请求给哪部电梯由 DispatchStrategy 决定。

请求容器不应该混入复杂业务策略。ElevatorRequestTable 目前承担了等待任务查询、筛选、取出,也有部分接收期辅助方法。若继续重构,可以把复杂筛选逻辑再下沉到策略类或查询对象中。

状态机应该集中管理状态变化。hw7 的 ShaftContext 相比把 UPDATE/RECYCLE 状态散落在多个 if-else 中,更容易保证状态前置条件、状态变化和 notifyAll() 的一致性。

6.3 三次作业带来的设计体会

hw5 时,简单的输入队列、调度线程、电梯请求表已经够用。因为电梯由请求指定,调度器只是转发器,很多边界可以由单部电梯内部处理。

hw6 加入检修后,原来的结构暴露出扩展性问题:电梯不仅有“有无请求”,还有“是否可服务”;乘客不仅会完成,还可能中途失败并重回调度;调度器也不能只看输入队列是否结束,还要看电梯是否都恢复空闲。

hw7 加入双轿厢后,边界必须重新划清。一个井道不再等价于一个电梯线程,主副轿厢之间需要共享状态,而这个共享状态不能由任一轿厢单独持有。因此 ShaftContext 成为必要抽象。

7. 核心类图

classDiagram
direction LR

class Main
class InputThread {
    <<thread>>
    +run()
}
class DispatchThread {
    <<thread>>
    -pendingTasks
    +run()
    -dispatchPendingTasks()
    -handleEvent()
    -canShutdown()
}
class ElevatorThread {
    <<thread>>
    +run()
    +wakeForControl()
    +isIdleForShutdown()
    +getDispatchSnapshot()
}
class DispatcherInbox {
    -queue
    -inputEnded
    -dispatcherClosed
    +addEvent()
    +getEvent()
    +setInputEnded()
    +closeDispatcher()
}
class ElevatorRequestTable {
    -waitingTasks
    -dispatcherClose
    -forceWakeUp
    +receiveTask()
    +pollBoardableAtFloor()
    +drillAllWaitingTasks()
    +awaitIfIdle()
}
class DispatchStrategy {
    +chooseElevator()
    +getSoleDispatchableElevatorId()
    +canDispatchTask()
}
class ElevatorStrategy {
    +nextAction()
    +determineBoardingDirection()
    +nextDirectionAfterService()
}
class ShaftContext {
    -state
    -mainFloor
    -spareFloor
    -mainReservedFloor
    -spareReservedFloor
    +acceptUpdateJob()
    +beginUpdateWithOutput()
    +finishUpdateWithOutput()
    +acceptRecycleJob()
    +finishRecycleWithOutput()
    +acceptMaintJob()
    +awaitMovePermit()
    +onArrive()
}
class CabinState {
    -currentFloor
    -doorState
    -direction
    -passengerCount
    -passengers
}
class DoubleCarRuntime {
    +runDoubleStep()
    +moveOneFloorInDouble()
}
class ControlPhaseRuntime {
    +runUpAcceptStep()
    +runUpdateSilentStep()
    +runRecycleAcceptStep()
}
class ElevatorDispatchSnapshot
class PersonTask {
    -stage
    -fromFloor
    -toFloor
    -activeElevatorId
    +startReceive()
    +cancelReceive()
    +board()
    +transferAt()
    +finishAtDestination()
}
class InputEvent
class PersonRequestEvent
class MaintRequestEvent
class UpdateRequestEvent
class RecycleRequestEvent
class RedispatchPersonEvent
class ElevatorUpdateEvent
class MaintJob
class ShaftState {
    <<enum>>
    Normal
    Rep_Accept
    Repair
    Test
    Up_Accept
    Update
    Double
    Rec_Accept
    Recycle
}
class TaskStage {
    <<enum>>
    New
    Waiting
    Onboard
    Finished
}
class TimableOutput

Main --> InputThread
Main --> DispatchThread
Main --> ElevatorThread
InputThread --> DispatcherInbox
DispatchThread --> DispatcherInbox
DispatchThread --> DispatchStrategy
DispatchThread --> ElevatorRequestTable
DispatchThread --> ShaftContext
DispatchThread ..> ElevatorDispatchSnapshot
ElevatorThread --> ElevatorRequestTable
ElevatorThread --> ElevatorStrategy
ElevatorThread --> CabinState
ElevatorThread --> ShaftContext
ElevatorThread --> DoubleCarRuntime
ElevatorThread --> ControlPhaseRuntime
ShaftContext "1" o-- "2" ElevatorThread
ElevatorRequestTable o-- PersonTask
CabinState o-- PersonTask
InputEvent <|-- PersonRequestEvent
InputEvent <|-- MaintRequestEvent
InputEvent <|-- UpdateRequestEvent
InputEvent <|-- RecycleRequestEvent
InputEvent <|-- RedispatchPersonEvent
InputEvent <|-- ElevatorUpdateEvent
ShaftContext --> ShaftState
PersonTask --> TaskStage
ElevatorRequestTable ..> TimableOutput
ElevatorThread ..> TimableOutput
ShaftContext ..> TimableOutput

Main 是启动层,负责创建 DispatcherInbox、所有电梯线程、所有请求表、所有井道上下文,并完成线程启动与 join()

InputThread 是输入层,只负责调用官方输入接口,把不同类型的请求包装为 InputEvent 子类,然后放入 DispatcherInbox

DispatcherInbox 是输入线程、电梯线程和调度线程之间的共享事件队列,是典型生产者-消费者缓冲区。

DispatchThread 是调度层,负责维护 pendingTasks、调用 DispatchStrategy、处理 RedispatchPersonEvent 和控制请求,并判断系统终止。

DispatchStrategy 是全局分配策略,只基于 PersonTask 和各电梯的 ElevatorDispatchSnapshot 评分,不直接修改电梯状态。

ElevatorRequestTable 是调度器与单部电梯之间的请求缓冲区,负责 RECEIVE 输出、任务入队、候乘查询、上客取出、空闲等待。

ElevatorThread 是执行层,负责普通运行、检修流程、控制期流程的分派。hw7 中它把双轿厢运行委托给 DoubleCarRuntime,把 UPDATE/RECYCLE 控制期委托给 ControlPhaseRuntime

CabinState 是单个轿厢的状态对象,保存当前楼层、门状态、方向、载客数和乘客列表。

ShaftContext 是 hw7 的井道共享状态对象,是主副轿厢线程之间最重要的锁对象。它统一管理 ShaftState、主副轿厢楼层、预约楼层、F2 等待标记和控制请求生命周期。

PersonTask 是乘客生命周期对象。它用 TaskStage 表示 New -> Waiting -> Onboard -> Finished,在 OUT-F 或检修/回收中途下车时通过 transferAt() 回到 New,重新进入调度器。

稳定依赖包括:输入层依赖事件队列、调度层依赖快照和请求表、执行层依赖请求表和策略、状态层被执行层和调度层读取。为适配迭代变化引入的依赖主要是 hw7 的 ShaftContextDoubleCarRuntimeControlPhaseRuntime,它们将双轿厢和控制请求从原本单轿厢模型中拆出。

8. 大模型使用心得

我使用的 AI 为 codex gpt5.5 xhigh。

在这个单元中,由于对一开始对多线程的概念较为模糊,我大量求助AI有关于多线程与锁之间的问题,很好的帮我理清了相关概念。

在后续作业开发中,我会首先提出我的架构设计,在与 AI 探讨后由 AI 生成一份代码骨架,我根据骨架完成代码。在 hw6 以及 hw7 中,我主要负责完成基本的功能部分,在通过弱侧和中测后让 AI 帮助我进行性能上的优化以及 code review。

我的使用感受就是 AI 能够基本上完成作业的要求,帮助我在时间紧迫时顺利完成一份质量还不错的代码。不过在审查部分,AI 无法照顾到每个细节,尤其是后期要求越来越复杂,有很多隐式的 bug 未能发现并解决,也会讲一些性能问题误判为正确性问题。

在这一单元中,我使用 AI 的频率和次数相较于上一单元大幅增加,这提高了我的工作效率,不过我也认为在使用 AI 的同时我自己的思考有所欠缺,似乎从自己写一份代码变成了理解 AI 的代码。尤其是当 AI 大量改动时,我并没有对每一个方法都进行自己的审查,只是直接用评测机等方法进行黑盒测试和回归测试,失去了锻炼代码能力的机会。

9. 体验与感受

经过这个单元的学习后,我对多线程并发有了更清晰的了解,对生产者-消费者模型也有了基本的掌握。

电梯一直像一个恐怖怪谈一样传递在每一届 6 系中间,但由于我大量使用了 AI,我并没有在这一单元遇到很大的麻烦,对代码的理解与掌握程度肯定也不如原来的学长学姐们,需要我反思在后续如何平衡好投入时间和代码效率。

在 hw5 中,我的多线程和共享对象架构都较为清晰,也是我对多线程理解最透彻的时候。但在后续作业里,感觉考察的重点由多线程转变为状态机设计、边界情况考虑和分派策略的优化,许多同学把大量的时间投入在了性能优化中,对多线程本身的考察可能不多,感觉不太契合单元主题。这点从互测阶段也能看出来,大部分同学攻击的都是由于分配策略导致的超时问题而不是多线程死锁相关的问题,是否可以增加对多线程本身的考察呢?

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

304

社区成员

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

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