OO_Unit2_Summary

OO第二单元总结

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

在三次作业的设计中,我都是通过对电梯的共享队列加锁来实现控制的。将对共享对象进行操作和访问的方法用synchronized修饰以处理读-读和读-写冲突。由于作业采取生产者-消费者的设计模式,因此只需对托盘(即请求队列加锁)即可解决线程不安全的问题,这种方法较为清晰且不易出错,在多线程的情况下能够稳定地保证线程安全问题。值得一提的是,在第二次和第三次作业中,由于会有多个电梯共享一个请求队列,因此可能导致本应被阻塞的线程在不恰当的情况下被notify,因此我们只需在对从队列中取请求和添加请求的方法加锁。

二、调度器设计

在第一二次作业中,可以将所有请求分为不同类,5个楼座内的纵向请求和10个楼层内的横向请求均可由对应的符合条件的单部电梯来完成。因此调度器的主要任务是将输入线程中得到的请求分配到对应的请求队列中去,至于具体由哪部电梯完成对应请求,使用自由竞争的方式处理。在第三次作业中,由于添加了只能到达特定楼座的横向电梯,当电梯从共享请求队列获取请求之后,需要特判自己是否有能力完成相应的请求。另外,一个请求被分成不同阶段来完成,当一部分任务完成时,将请求重新加回waitQueue重新由调度器进行分配,直至请求全部完成。

调度器本身作为一个线程,动态地对当前waitQueue中的请求进行分配。

 

 

三次作业中,调度器完成的均是waitQueue到processingQueue这一步任务。

当前waitQueue为空时,调度器线程在getOnePerson中被阻塞,直到有新的请求添加进来,避免了轮询。

Person person = waitQueue.getOnePerson();
if (person == null) {
  continue;
}

三、架构模式

三次作业均采用了生产者-消费者的设计模式,输入线程作为生产者,请求队列作为托盘,电梯作为消费者。第三次作业中,在输入线程获取到一个新的请求时,先将该请求静态划分成不同的完成步骤,再依次完成请求,每完成一部分,就将请求返回到托盘中去进行下一步,这实际上实现了工厂(流水线)模式。

 

 

由于waitQueue、processingQueue、elevatorQueue都通过requestQueue来实现,所以UML类图的构建看起来比较混乱,从可维护的角度上来说,这三个队列应该由三个不同的类来管理更加合适。

第三次作业的UML类图

这里由于三个队列都由requestQueue实现,因此逻辑关系稍显混乱。

 

 

第三次作业的UML协作图

为了更便于理解,这里我们将waitQueue,processingQueues分开表示。

 

 

程序采取这样的三级分配机制,根据请求的特点逐级分配任务,具有较为清晰的架构,三者用统一的对象管理,减小了设计的复杂度。

不足的是,在刚接收到请求时就将其的换乘楼层确定,这种设计方式不太灵活,另外,对于复杂的指令,比如需要通过两次横向电梯的换乘解决的请求,会发生绕路的情况,按照“纵向-横向-纵向”的顺序完成请求(这点建立在1层的横向电梯可到任意座的基础上),如果取消初始的“万能电梯”,程序的算法需要有较大的改动。

 

四、自己程序的bug

本单元的三次作业在互测环节均被发现了bug。

第一次作业

忘记了给输出加锁,由于官方输出包是线程不安全的,出现了Output not in order的情况。

Solution

通过新增一个加了同步锁的输出方法解决。

第二次作业

第二次作业在多部电梯共享请求队列时,一个线程判断队列是否为空的操作唤醒了其他被阻塞的线程。

Solution

只在对共享队列进行存取请求时notifyAll。

第三次作业

判断横向的电梯是否能够完成当前请求时的判断条件有漏洞,导致电梯接受了完成不了的请求。

Solution

仅当电梯在请求的起点和终点均能开门时接受请求。

public boolean isAble(Person person) {
   if (type.equals("floor") &&
          (((switchInfo >> (person.getNowToBuilding() - 'A')) & 1) == 0 ||
          ((switchInfo >> (person.getNowFromBuilding() - 'A')) & 1) == 0)) {
       return false;
  }
   else {
       return true;
  }
}

五、别人程序的bug

三次互测均未发现别人的bug。

六、心得体会

1.线程安全

刚开始初接触多线程时,不明白哪里应该设为同步块,哪里需要加锁以保证线程安全。在实际操作中发现,绝大多数线程安全问题均可以通过同步控制来解决,保证任何时刻都不存在读-写冲突或写-写冲突。只需要认识到哪些对象是共享对象,并对其施以足够的保护,即可保证不会出现冲突。

2.层次化设计与设计原则

本单元的层次化设计出要体现在请求的分类与分配上。在我的设计中,所有输入的请求首先分配到waitQueue中去,然后通过调度器根据请求的当前状态分配到各层(座)的共享队列中,此后,各层(座)的电梯从共享队列中取请求,完成的请求检查是否到达目标位置,若没有到达,则将请求返回到waitQueue重新分配。这种做法保证了每个请求不会遗漏或在不同地方同时进行,有着较高的稳定性。

SRP

程序每部分的职责应当单一,我的Elevator类既保存了个电梯的信息,又控制了电梯的运行逻辑,这种做法使我在调试的过程中遇到了不少困难。单一职责原则,实际上保证了代码的可维护性。

LOD

LOD原则保证代码的耦合度较低,越多的类实现交互,我们得到的UML类图就杂乱无章,程序也显得混乱不堪。设计中需要注意,如果一个方法放在本类中,既不增加类间关系,也对本类不产生负面影响,那就放置在本类中,这样能够减少代码的耦合度,增强可读性和维护性。

 

和第一单元的设计一样,合理的架构和良好的层次划分对设计的可拓展性和维护性都大有裨益。

posted on 2022-04-26 20:08  shuo1337  阅读(29)  评论(1编辑  收藏  举报