面向对象第二单元总结博客

许彤-24373244 2026-04-25 22:29:24

面向对象第二单元总结博客


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

1. 同步设计的整体演化

在三次作业中,同步设计经历了单锁模型 → 分层锁模型 → 协同锁模型的过程

2. 第一次作业:单对象锁

synchronized (this) {
    waiting.add(p);
    notifyAll();
}

所有共享数据由同一把锁保护,实现方式简单。

3. 第二次作业:分层锁模型

利用生产者-消费者模型

新增了:

  • MainQueue(全局请求)
  • SubQueue(电梯私有请求)
  • ElevatorStorage(状态共享)
public synchronized void addPassenger(Passenger p)
while (queue.isEmpty() && !isAllEnd()) {
    wait();
}

为了减小竞争,在每个模块独立加锁,并使用wait/notifyAll实现线程通信,避免busy-wait。

4. 第三次作业:协同锁模型

新增了:

  • Coordinator(协调器)
  • 双轿厢资源竞争控制
public synchronized void applyForChangeFloor(int elevatorId)
        throws InterruptedException {
    while (inChangeId != 0 && inChangeId != elevatorId) {
        wait();
    }
}

在第三次作业中,通过类似信号量的机制实现多线程之间的协同控制,使得换乘楼层在同一时刻只能被一个电梯占用,从而避免冲突并保证系统正常运行。

5. 锁选择总结

阶段加锁对象加锁方式实际作用
HW5Elevator(每个电梯对象)synchronized(this)保护 waiting / inside 队列,保证上下乘客时不冲突
HW6MainQueue / SubQueuesynchronized 方法 + wait/notifyAll实现生产者-消费者模型,调度器与电梯解耦
HW7SubQueue + Coordinator + ElevatorStorage多对象锁 + 条件等待(wait)控制换乘楼层、双轿厢协同、维护/更新请求

6. 锁与同步代码的关系

在多线程程序中,加锁的首要作用是保护共享变量,保证多个线程在访问同一数据时不会发生冲突。例如对队列进行插入操作时,需要通过 synchronized 进行保护,从而保证队列状态始终一致,不会出现数据覆盖或丢失的问题。

synchronized (this) {
    queue.add(p);
}

在实际编写同步代码时,必须保证“条件检查”和“后续操作”是一个原子过程,即二者必须在同一把锁的保护下完成。如果在检查条件和执行操作之间没有加锁,就可能在多线程环境下出现竞态条件,导致出错。

synchronized (this) {
    while (queue.isEmpty()) {
        wait();
    }
}

此外,wait 方法必须在已经获得对象锁的前提下调用,否则会抛出异常。这是因为 wait 本质上会释放当前锁并进入等待状态,如果没有持有锁,就无法正确管理线程的挂起与唤醒。

synchronized (this) {
    wait();
}

最后,notifyAll 的作用是在线程修改了共享状态后,通知所有正在等待该条件的线程重新进行判断。它并不直接唤醒某个特定线程执行,而是提供一种“状态已变化”的信号,使等待线程有机会重新竞争锁并继续执行。

synchronized (this) {
    queue.add(p);
    notifyAll();
}

二、调度器设计与线程交互

1. 调度器设计

(1)第一次作业

在第一次作业中,采用了较为简单的分配方式,由Dispatcher线程直接根据输入将乘客请求分配给指定电梯,没有什么复杂逻辑。

Elevator e = elevators[r.getElevatorId()];
e.addPassenger(p);

(2)第二次作业

在第二次作业中,引入了独立的调度器线程(Scheduler)和影子电梯(ShadowElevator),输入线程先把请求放入 MainQueue,调度器再从主队列中取出请求,并根据各个 ShadowElevator 中保存的电梯状态计算代价,选择更合适的电梯。

Passenger p = mainQueue.takePassenger();
int elevatorId = bestElevator(p);

相比第一次作业有了明显的改变,调度决策不再固定,而是基于当前系统状态动态计算,从而可以在一定程度上优化响应时间和负载分布。同时,调度器的引入也使系统结构更加清晰,实现了“请求产生”和“请求分配”的解耦。

(3)第三次作业

在第三次作业中,在双轿厢、电梯更新、维护等复杂场景下,调度需要考虑资源冲突与协同问题,使得调度逻辑更加复杂但也更加接近真实系统。这种调度方式能够根据系统实时状态进行决策,并动态适应环境变化。

2. 调度器与线程交互机制

在系统设计中,各线程之间通过队列进行解耦与协作,整体结构可以表示为:

InputThread → MainQueue → Scheduler SubQueue → Elevator

其中,输入线程负责不断产生乘客请求并放入主队列;调度器线程从主队列中获取请求,根据当前各电梯的状态进行分配;各电梯线程则从对应的子队列中获取请求并执行。这种分层结构使得“请求产生”“请求分配”和“请求执行”相互独立,降低了线程之间的耦合度。

在具体实现中,线程之间的交互主要通过阻塞与唤醒机制完成。例如调度器通过以下方法从主队列获取请求:

mainQueue.takePassenger();

随后将请求分配给对应电梯:

sq.addPassenger(p);

而电梯线程则通过等待机制获取任务:

subQueue.waitForRequest();

这种基于 wait/notify 的方式,使线程在没有任务时能够阻塞等待,从而避免无意义的CPU占用。

3. 调度策略分析

在调度策略上,采用了基于代价函数的动态调度方法。调度器会遍历所有电梯的影子状态(ShadowElevator),并计算每个电梯执行该请求的代价,最终选择代价最小的电梯。

int perf = shadow.judgeCost(p);

在完成代价函数时,我综合考虑了多个因素:电梯到达乘客出发楼层的距离、电梯当前负载以及已有等待任务数量等,可以表示为:

travelTime + personCost + waitCost

这种方法能够在多种因素之间进行权衡,使调度更合理。

4. 多性能指标分析

在具体实现中,调度策略实际上同时考虑了时间、电量和负载等多个性能指标。

在时间优化方面,计算电梯到乘客出发楼层的距离来估计响应时间:

distToFrom * 400

这种方式可以优先选择距离较近的电梯,从而减少乘客等待时间,提高系统响应速度。

在电量优化方面,虽然没有直接计算能耗,但通过减少无效移动和不必要的折返,可以间接降低电梯运行的总路径长度,从而实现隐式的节能效果。

在负载均衡方面,考虑电梯当前已经分配但尚未完成的任务数量:

personWait * 200

可以避免将过多任务集中到某一部电梯上,从而使系统整体负载更加均衡,减少拥塞现象。

三、Bug分析与调试方法

1. 常见Bug类型

在完成练习中,最常见的问题是CPU超时,基本上都是因为出现轮询而导致运行时间过长。这类问题通常出现在未正确使用wait/notify机制的情况下。

此外,状态不一致也是一个较为隐蔽但影响较大的问题。比如说调度器重复分配同一乘客、电梯未正确维护receive状态,或者乘客在换乘过程中丢失等。

2.Debug方法总结

本单元我经常将自己的代码与其他同学放入评测机进行对拍,通过评测机生成的测试数据进行debug。

另外,在调试过程中,可以重点关注电梯的当前楼层、运行方向、轿厢内乘客数量以及等待队列的状态,通过这些信息判断系统是否合理。

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

在多线程程序中,线程安全的核心在于保证共享数据在并发访问下仍然保持一致性,同时避免出现竞态条件。具体来说,当多个线程同时读写同一数据时,如果没有适当的同步机制,就可能导致数据状态混乱,进而引发错误。因此,在设计中需要通过加锁或线程通信机制,确保关键操作具备原子性,从而维持系统的正确性。

我们可以将系统划分为多个职责清晰的模块:输入层负责产生请求,主队列负责统一存储请求,调度器负责分配请求,而电梯线程负责执行具体操作。各层之间通过队列进行通信,而不是直接共享复杂数据结构,从而降低了模块之间的耦合程度。

层次化设计带来的优势主要体现在三个方面。首先,它能够有效降低系统耦合,使各模块可以独立开发和修改;其次,清晰的结构有助于提高代码的可读性和可维护性;最后,当系统需要扩展(时,可以在不影响其他模块的情况下进行局部修改。

在多线程环境下,锁的设计需要与层次结构相匹配。一个重要原则是“每一层只管理自己的锁”,即每个模块只对自身的数据负责加锁,而不跨层控制其他模块的数据。这种设计可以有效避免锁之间的相互嵌套,从而降低死锁发生的概率。相反,如果多个模块之间相互加锁,很容易形成循环等待,导致系统陷入死锁。

五、大模型使用心得

在本次作业中,我尝试利用大模型来辅助搭建了评测机。并将性能评分公式提供给大模型,在快速生成测试数据同时,还能并对程序的运行结果进行分析,大大降低了debug的复杂度和难度。

六、第二单元体验与建议

在本单元的学习过程中,我对多线程编程和系统设计有了深入的理解。通过三次作业的逐步推进,不仅掌握了线程同步与调度的基本方法,还对如何设计一个复杂系统有了更清晰的认识。这一过程从最初的功能实现,逐渐过渡到对性能与结构的综合考虑,对编程能力提升较大。

然而,本单元也存在一定难点。首先,多线程程序的调试难度较高,由于线程执行顺序具有不确定性,错误往往难以复现。其次,系统整体复杂度较高,涉及多种状态和分支逻辑,容易在某些边界情况下出现问题。最后,在性能优化时需要做好在正确性和效率之间的平衡。

本单元的难度很大,希望可以多提供一些debug上的指导吧。

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

307

社区成员

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

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