309
社区成员
发帖
与我相关
我的任务
分享在本次的三次多线程作业中,我主要采用了经典的生产者-消费者模型来实现各个线程间的数据交互。为了保证数据的安全性,必须要谨慎地设置同步块和锁。
在我的设计中,主要的共享资源是全局的总请求队列 RequestQueue 和每部电梯专属的 ElevatorRequestPool。
this关键字),通过给类内部的方法加上 synchronized 关键字来实现互斥。比如在 RequestQueue 中,offer 和 poll 方法都被声明为同步方法。ElevatorThread 和 DispatcherThread)中,我尽量缩减了同步块(临界区)的范围。例如在电梯运行的主循环中,我只在检查电梯是否需要停止、以及是否需要等待新任务时,使用 synchronized (requestPool) 包裹 wait() 逻辑。而在电梯开门、移动等耗时且不需要锁的操作(如 Thread.sleep())时,坚决不占有锁,这有效避免了其他线程饿死或产生死锁的情况发生。调度器在我的架构中被抽象为了一个独立的 DispatcherThread 线程。
RequestQueue 中取出(消费)用户的乘梯请求或系统发出的改造、检修等控制请求。ElevatorRequestPool 中,随后对应电梯的 ElevatorThread 会被唤醒并执行具体任务。ElevatorSelector 类来实现基于“代价函数(Cost Function)”的分配策略。
Shaft 类中加入了位置判断。前期由于 Shaft 的锁与各自 ElevatorRequestPool 的锁存在交叉申请的情况,导致过严重的相互等待死锁。wait() 时,因为判断条件没有写好,导致线程被异常唤醒后没有进入阻塞状态,疯狂消耗 CPU 资源。while 循环条件是不是写反了导致死循环;如果运行结果是未输出结束标记,则顺着时间线倒推哪一步卡住了。经过本单元的锤炼,我最大的体会是:良好的层次化设计是保证线程安全的最好方法。
如果把所有的处理逻辑和状态改变都揉在一个类里,加锁就会变得像一团乱麻。在我的代码中,我按照“输入层 -> 调度层 -> 电梯执行层”进行了分层处理。
共享数据(各种队列)被提取出来独立成类,并在这些数据类内部封装好所有线程安全的 public synchronized 方法。这样一来,业务逻辑层(调度器和电梯本身)根本不需要关心底层是如何加锁、释放锁的,只需要无脑调用安全接口即可。这种“让数据保护自己”的层次化设计,极大地降低了线程安全问题的发生概率。
switch-case 基础骨架,极大地减少了前期敲写模板代码的繁琐工作。wait() 和 notifyAll() 的精准控制,大模型也常常会漏掉关键的边界条件。体验与感受: 从单线程过渡到多线程,思维方式的转变确实带来了一定的挑战。初期由于对 synchronized 和 wait/notify 机制的应用不够熟练,遇到了一些诸如死锁或线程无法正常结束的问题。但随着不断修改和查阅资料,理清了共享数据和锁的依赖关系后,看着自己设计的调度器能够有条不紊地指挥多部主副轿厢完成各种接送、改造和检修任务,整体的系统也按照预期顺利运转起来,还是非常有收获感和成就感的。
建议: 考虑到同学们在刚接触并发编程时,遇到死锁或者 CTLE 等问题往往容易无从下手,建议课程组可以在第二单元开始时,提供一份简易的“Debug指南”或“常见并发踩坑手册”。比如分享一些通过控制台日志排查死锁的实用技巧,或者简单介绍一下分析多线程报错的常规思路。这样可以在前期帮助大家减少毫无头绪的试错时间,把更多精力放在系统架构的思考上。