OO第二单元总结

OO第二单元总结

总览

  • 三次作业任务依次为单电梯、多电梯、换乘多电梯。
  • 均使用了双层生产者-消费者模型。
  • 后两次作业由于分配请求需要知晓电梯状态,引入观察者模式向分配器更新状态。
  • (其中第一次作业由于为单电梯,省去了请求分发正确电梯线程)。
  • 其余的两次作业均为4个线程,后文作具体解释。

第一次作业

本次任务为模拟单部电梯

类图

image

时序图

image

思路分析

第一层生产者-消费者

  • MyInput为生产者,实时向托盘Scheduler读入请求。

  • Scheduler为托盘,采用单例模式,保存线程安全队列<Person>ArrivePattern

  • Updater为消费者,为电梯子类,实时从托盘读出请求,待电梯在合适时间取用。

第二层生产者-消费者

  • Scheduler为生产者,实时向Updater输入请求。
  • Updater为托盘,保存请求数组。
  • Elevator为消费者,在到达每层时从托盘中取走该层出发的请求。

调度策略

  • 在SSTF的基础上增加:当电梯内有人时,目标楼层为最近上楼或最近出楼层的。

同步块的设置与加锁

  • 同步处理采用线程安全类(HashTable、ArrayBlockingQueue、Vector)
  • 对两层生产者-消费者模型托盘(分别为SchedulerUpdater)的读写均需要同步处理。
  • 特别的,ElevatorUpdater进行组合(读/写)操作时需要额外加锁(sychronized)。

复杂度分析

image

  • 从图中可以看出,Elevator类中的move方法复杂度较高,主要是因为其承担了结束线程的if条件判断较为复杂,导致方法复杂度的升高,现在看来可以将该判断单独抽象为一个方法降低复杂度。

Bug分析

本次作业强测和互测均未出现bug。

第二次作业

本次任务为模拟多部电梯

类图

image

时序图

image

思路分析

第一层生产者-消费者

  • MyInput为生产者,实时向托盘Scheduler读入请求。

  • Scheduler为托盘,采用单例模式,保存线程安全队列<Person>ArrivePattern

  • Dispatcher为消费者,也采用单例模式,实时从托盘中取出请求。

第二层生产者-消费者

  • Dispatcher为生产者,按分配规则实时向各个电梯的Updater输入不同请求。
  • Updater为托盘,保存请求数组。
  • Elevator为消费者,在到达每层时从托盘中取走该层出发的请求。

调度策略

  • 单电梯策略不变。
  • 调度器为请求指定电梯时,选择目前离出发点最近的电梯(这点导致了强测性能点的RTLE)

观察者模式

  • Elevator在到达每层时向Dispatcher发布自己的状态变化。

同步块的设置与加锁

  • 同步处理采用线程安全类(HashTable、ArrayBlockingQueue、Vector)
  • 对两层生产者-消费者模型托盘(分别为SchedulerUpdater)的读写均需要同步处理。
  • 观察者模式中对status的读写需要同步处理。
  • ElevatorUpdater进行组合(读/写)操作时需要额外加锁(sychronized)。
  • 观察者模式中Dispatcher对电梯状态进行组合(读/写)操作时需要额外加锁(sychronized)。
  • 涉及新增电梯操作,对电梯数组Elevators的读写也需要同步处理。

复杂度分析

image

image

  • 从图中可以看出,与第一次作业基本相同,Elevator类中的move方法复杂度较高,主要是因为其承担了结束线程的if条件判断较为复杂,导致方法复杂度的升高,现在看来可以将该判断单独抽象为一个方法降低复杂度。

Bug分析

本次作业强测出现一处bug,有两个点未通过(唯二的性能点)。

  • 原因也很简单,特殊情况性能太差,出现请求扎堆。

  • 具体来说是我在分配请求给每个电梯时未考虑电梯已承担请求的数量,对单个请求只用是否独立最优来分配电梯,这导致了若同时到达大量类似请求(出发、到达楼层很近),会全部被分给同一电梯(请求扎堆),导致超时。

  • 修复策略为将已承担请求作为分配标准之一:在多于某个数量后优先考虑其他电梯。

第三次作业

本次任务为模拟多部可换乘电梯

类图

image

时序图

image

思路分析

第一层生产者-消费者

  • MyInput为生产者,实时向托盘Scheduler读入请求。

  • Elevator也为生产者,将换成乘客后重新放入Scheduler托盘。

  • Scheduler为托盘,采用单例模式,保存线程安全队列<Person>ArrivePattern

  • Dispatcher为消费者,也采用单例模式,实时从托盘中取出请求。

第二层生产者-消费者

  • 与上次作业相同

调度策略

  • 单电梯策略不变。
  • 调度器策略沿用上次bug修复时的策略。
  • 换乘策略,设置3、9、15三个换乘点,手动打表设置所有情况的换乘路径(设置思路为在保证可以运送的基础上优先级C>B>A)(这20*20/2的大量数据也给我带来了一个抄写bug)。

观察者模式

  • 与上次相同,Elevator在到达每层时向Dispatcher发布自己的状态变化。

同步块的设置与加锁

  • 虽然加入换乘策略,但不需要对Person进行多线程读写,因此与上次加锁策略相同。

复杂度分析

image

image

image

  • 从图中可以看出,本次作业新增的复杂度过高的方法为Dispatcher.getRandomId(),该方法其实是上次bug修复时引入的考虑电梯已承担请求来分配请求导致的高复杂度,当时处于时间问题没有抽象为单独的方法,现在看来,可以以此降低此处圈复杂度。

Bug分析

本次作业强测出现一处bug,有两个点未通过,错误原因为数据抄写错误。

  • 我是打表填写换乘策略,从Excel誊写到Idea时(FROM-9-TO-16)出现手误。

  • 很巧错误地写成了在9楼换乘(整体路径为9-9-16,此时第一段由于每层我采取先下后上会被关在电梯里)

  • 覆盖测试不够严密,没有使各个楼层情况完全独立,于是该错误情况被其他数据干扰携带顺利完成。

  • 因此这个bug可以说是出在了我的覆盖性正确测试中。

关于测试

  • 黑盒测试(自测、互测基本相同)
    • 主要为功能正确性测试
    • 随机生成输入很难覆盖到线程bug
    • 自己构造,主要为密集输入(攻击线程安全)、输入EOF的相对时间(线程结束)
  • 白盒测试(自测时关注的检查点为互测主要攻击点)
    • 方法抽象,降低复杂度(很麻烦但确实很有必要)
    • 每个boolean取值的情况都要覆盖到
    • 关注死锁,静态检查上锁情况
    • 尽量去锁化,能放锁的地方放,用流程保证正确性

可扩展性

本单元作业具有良好的可扩展性,迭代开发较为顺利

  • 后两次作业从类图来看架构完全相同
  • 单电梯运行策略沿用三次作业
  • 指令分配策略在第二次作业引入并沿用后两次作业
  • 换乘与结束程序均不改变任何架构设计,采用从自行构造特殊输入请求(如Id=-1/-2),换乘时修改出发到达楼层,这体现了良好的扩展性。
  • 但就不同到达模式来说,确实很难平衡性能与可扩展性,我也只针对单电梯构造了不同但运行策略,对于多电梯的分配甚至后续的换乘,均采用了单一的分配策略,但也取得了很好的性能分(最后一次我甚至关掉了单电梯的模式优化开关,性能表现反而更好)

线程安全

  • 主要的关注点为线程的开始与结束,枚举具有随机性的开始结束顺序,保证各种情况不会出现异常。
  • 共享对象的访问的原子操作通过各种线程安全类(HashTableVectorArrayBlockingQueue等)来保证,特别注意,对此类对象的组合操作要额外加锁。
  • 枚举考虑wait与notifyall的结合使用的各种情景。

心得体会

  • 整体难度小于第一单元,但测试难度更大,因此从分数/时间收益来看其实差不多。
  • 本单元第一次编写测评机,显著提高了测评效率,但仍有亟待完善之处。
  • 多线程的bug具有随机性,需要采取更全面以及多次的测试策略。
  • “大道至简”——最后一次作业发现不针对不同到达模式优化反而或许可以获得更高的性能分。
  • 设计模式的学习与应用将极大地提高架构的安全性与可扩展性。
posted @ 2021-04-27 21:24  LNTisNotaTree  阅读(79)  评论(0)    收藏  举报