301
社区成员
发帖
与我相关
我的任务
分享Unit2总结——多线程
由于第一次接触多线程,在整个单元的作业迭代过程中遇到了许多困难。多线程不同于之前的单一线程,只需要考虑顺序执行的过程中是否会出现逻辑上的bug。多线程安全问题,死锁问题和CPU运行时间都需要在此次作业中考虑。
由于多线程会同时访问一个共享对象,因此需要清楚所有的共享对象,以及如何对共享对象进行加锁来避免出现线程安全的问题。
sychronized对两个共享对象进行保护即可。Reset指令。为了保证进程不会提前结束,因此需要记录请求中Reset指令的数量。本人专门创建了一个ResetCnt用于记录,则其为新增的共享对象。此外,若使用了影子电梯的调度策略,电梯线程和调度线程都会对电梯进行访问,因此电梯也是共享对象。对于新增共享对象的处理同第一次作业相同。调度器的功能是负责将总请求池中的请求分配给每一个电梯。用生产者-消费者模式来解释,对于总请求池,调度器是消费者;而对于每个电梯的请求队列,调度器是生产者。
还有一点需要注意,就是调度线程的结束条件。就第一次作业而言,只需要总请求池waitingQueue为空并且isEnd为真,则调度线程结束;但是第二、三次作业中增加了Reset指令,电梯会发生重置(生产请求放入总请求池中)。因此需要将调度线程的结束条件更改为;waitingQueue 为空 && waitingQueue isEnd && Reset指令全部结束。
Request都已经指定好了分配的电梯。Request分配给处理时间最短的电梯。对于影子电梯的实现有两点需要注意,在第二次作业中新增的Reset指令会对电梯进行重置。如果模拟过程中也出现Reset则会导致该影子电梯的处理时间与实际情况不符。因此,在对电梯运行状况进行克隆时移出了Reset指令,以此来保证影子电梯模拟的尽可能合理。而第三次作业新增了双轿厢电梯,影子电梯模拟其换乘过程十分复杂,因此采用了与第二次作业相同的模拟方式,但是对最后的处理时间进行了修正(加上了请求换乘所需的时间)。本次作业需要创建三类不同的线程,包括:InputThread,Schedule,ElevatorThread。对于InputThread,该线程的作用是将所有输入的请求都存入一个总请求池(waitingQueue)。对于Schedule,该线程将waitingQueue中的请求分别调度给每一个电梯线程的请求队列。而ElevatorThread,则是处理对应电梯中请求队列的请求。在ElevatorThread中,有一个Strategy类用于处理电梯内部的运行策略,每次调用Strategy都会为电梯返回一个建议,决定电梯是移动还是开关门。
由上可知,本次作业采用了生产者-消费者模式。对于总请求池,InputThread生产请求,Schedule消费请求;对于每个电梯的请求队列,Schedule是生产者,而ElevatorThread是消费者。


相较于第一次作业,Request分为了PeopleRequest和Reset两个子类。并且还新增了一个SImulateElevator的线程,用于模拟电梯的运行。ResetCnt类的加入则是为了方便处理调度线程的结束。


第三次作业新增了一类Reset指令,其能将电梯重置为双轿厢电梯。因此此次作业中将Reset分为了ResetFirst和ResetSecond两个子类,分别用来储存正常重置和双轿厢重置指令。并且还新增了DoubleElevator用于处理双轿厢电梯。由于ResetSecond会将电梯改造成双轿厢电梯,而双轿厢电梯相当于是两个有楼层限制的电梯在同时运行,因此借助ManageDoubleElevator将原有的线程销毁然后创建两个新的电梯线程,通过这种方式来实现双轿厢电梯。


双轿厢的碰撞只会发生在换乘楼层,因此只需要注意两个轿厢在换乘楼层的行为。
Flag的实现如下:
sychronized关键词的理解不到位,对于共享对象没有进行有效的上锁,导致出现了线程安全的问题 。Reset指令在接受后相应的电梯只能移动两层,导致强测时出现了严重的错误。最终采取优先分配和处理Reset请求来修复Bug。print方法,通过输出相应的对象内容推断出bug所在。通过这三次作业的迭代,我对线程安全和层次化设计有了更加深入的理解。在多线程代码中,资源的共享与竞争无处不在。因此我们需要清楚代码中所有的临界资源,并且在访问的过程中确保共享资源的正确性与一致性。如何正确且有效的使用锁,同样也是保证线程安全的一个重要因素。对于层次化设计,一个好的架构设计不仅能够便于代码本身的维护,同时也便于后续的迭代与功能开发。在本单元的作业中,我采用了生产者-消费模式进行设计。该设计方式有效的减轻了我的设计压力,也让我进行迭代时思路更加清晰。