269
社区成员




该次作业中,在输入线程和电梯线程之间,我设置了一个 RequestQue 类用来缓冲乘客的请求,该类的除构造方法外的所有方法都是同步块,我使用了 synchronized 对方法上锁(本来想试一下读写锁,但发现在架构中并不存在“多读、少写”的特点,使用读写锁意义不大)。使用锁保证了同步块中处理语句在同一时刻只有一个线程能够执行。
在该次作业中,我在输入线程和电梯线程之间新增了一个调度线程Scheduler,从控制台中读入的请求不会直接进入电梯的请求缓冲区RequestQue,而是先进入输入线程和调度线程之间新增的InputQue缓冲类,然后由调度线程对到来的请求进行分配(平均分配)。InputQue类与第一次作业的RequestQue类似,除构造方法外的所有方法都使用 synchronized 对方法上锁。
该次作业中我新增了UpdateCheck线程用于管理电梯的update任务,涉及同步块的新增类只有UpdateWait类和Overlap类,UpdateWait类起到辅助Update任务的作用,具体为UpdateCheck线程在UpdateWait类上等待相关电梯完成准备工作,相关电梯线程完成准备工作后进入UpdateWait类通知UpdateCheck线程后wait,UpdateCheck线程被唤醒后进行update相关工作,UpdateCheck线程完成任务后唤醒电梯线程,使它们离开UpdateWait类继续工作。Overlap类负责管理重叠的楼层,避免电梯相撞,具体实现细节见下文。在这里UpdateWait类、Overlap类和之前的 RequestQue、InputQue类相同,除构造方法外的所有方法都使用 synchronized 对方法上锁。
第一次作业中,我并没有设计调度器,而是直接通过输入线程给电梯分派任务。在第二次作业中,我将调度器单独设计为一个调度线程,调度线程从InputQue缓冲类中获取从输入线程中传过来的请求,然后调度线程将相关任务通过 RequestQue类分派给相应的电梯线程,完成任务的获取与分派。在第三次作业中我沿用了这样的调度器设计。
我的调度策略使用最为朴素的平均分配,即从电梯1开始分配,分配一次任务后就从下一个电梯开始分配(但电梯不能够处理该请求则转向下一部电梯)。
最终的作业架构类图如下:
从中可以看到较为清晰的层次关系,即从输入线程、输入缓冲、调度线程到电梯线程过程中,各种请求从读入、分配到最终完成。
各个线程间的UML协作图如下:
从中可以看到,除创建和启动外,输入线程只与调度线程交互,而调度线程主要与电梯线程交互,电梯线程主要与调度线程交互,而Update管理线程只在有update任务时诞生,与电梯线程交互并完成update任务后终止。
稳定的是输入线程的实现,调度线程的实现也具有一定的稳定性,迭代中新增的内容很少。
不稳定的是电梯线程以及附属于它的策略类,每次迭代都新增了很多的内容,从第一次作业到第三次作业几乎脱胎换骨。
总之,稳定的是读取、分配任务的方式、不稳定的是实现任务的过程(这些过程很难统一,所以变化很大)。
在第三次作业中,我新增了Overlap类来管理两个电梯的重叠楼层,避免两个电梯相撞。Over拉票类只有一个属性beUse,其初始值为FALSE。在两个电梯update后,两个电梯都获得了同一个Overlap类,当电梯需要进入重叠楼层时,调用Overlap类的useFlo方法,当beUse为FALSE时,表示当前楼层没有电梯,否则有电梯。没有电梯时,该线程将beUse置TRUE,然后退出,继续接下来的操作;如果有电梯该线程原地wait()。当电梯离开重叠楼层时,调用Overlap类的outOfFlo方法,将beUse置FALSE,并唤醒可能存在的等待线程。并且当电梯处于重叠楼层时,如果不执行开门操作,则立即离开重叠楼层,保证另一个电梯有限等待重叠楼层。通过这种方法,我保证了在重叠楼层中不会出现两个电梯。
在该次作业中,我一共出现了两个bug:1.电梯无法正常开门,当时判断电梯能否开门的逻辑有误,在特殊情况下无法开门;2.电梯运行方向出错,当电梯第一次接受乘客时,我选择让当前楼层的乘客全部进入电梯,然后根据第一个人的目标楼层决定前进方向,当时但该方向的乘客全部下电梯时,我并没有更新电梯前进方向,所以出错。
该次作业中没有出现bug。
该次作业中仅出现一个bug,但它具有很强的共性,让我中测全过但强测 4 分。即当电梯 update 结束后,我并没有更新先前RECEIVE的乘客,而是选择直接“继承”已经RECEIVE过的乘客。但是当电梯update后,会有一些楼层无法到达,所以如果有乘客处于无法到达的楼层时,电梯既无法完成任务,也不能将他们送还调度池重新分配,所以电梯线程无法正常结束,TLE了19个样例。(而且在迭代过程中我已经实现了相关逻辑,但后面调试过程中“不小心”误删了……)
1.通过设置IDEA的断点调试;
2.因为多线程的关系,有时通过设置断点无法复现bug,所以采用最为原始的“print”法,输出相关状态进行调试;
通过三次作业,从零开始深入地了解了多线程的有关知识,理解了“锁”的相关概念,还认识到了线程安全对于程序的重要性,并学会了如何通过设置同步块让多个线程进行协作。
此外在第一单元的基础上,进入第二单元时我对于层次化设计更加得心应手,也让我的架构从第一次作业开始就足够清晰,使得后面没有重构的必要。