BUAAOO_UnitTwo

目录

一、设计策略

  • 第一次作业
  • 第二次作业
  • 第三次作业

二、第三次作业可扩展性分析——SOLID原则为例

三、度量分析

  • 第一次作业
  • 第二次作业
  • 第三次作业

四、Bug分析

五、Hack策略

六、心得体会

 

引言

  第二单元的三次作业是关于电梯问题的迭代思考。这一单元是我们对多线程问题的初次学习与接触。多线程问题中考察的重点是锁的占有与释放,处理不好就极易造成死锁;同时,由于多线程运行的不确定性,我们写代码与debug的难度都大大增加。

 

一、设计策略

第一次作业

  • 第一次作业是单部电梯的可稍带调度,我在这次作业中采用了消费者——生产者模式的思路。
  • 首先构建一个托盘类Tray,一个生产者线程InputProducer,一个消费者线程ElevatorConsumer,思路与课堂上讲过的样例基本相同。
  • 此次作业中虽然我构建了Controller类想完成调度器的功能,但由于设计时的考虑不周,最终的调度大部分还是由电梯自己实现的。
  • 我的调度策略是用queue来存储托盘接收到的所有请求,用Elevator的内部request来存储电梯内正在执行的情求,每到达一层,先对request进行分析,若desti==floor则下客,sleep开关门时间后,再对queue分析,若from==floor则上客。
  • 我选择在电梯到达的时刻立即下客,并且在电梯即将关上门的瞬间上课,以期接到尽可能多的乘客。
  • 我的本次调度性能尚可,现在想来还可以改进主需求的选择,设置成路径最长的一个,而不是简单的取head;

第二次作业

  • 第二次作业是第一次作业的简单扩展,将电梯数变为不确定的1-5个,并设置了最大载客限制。
  • 我仍然采用了第一次作业的基本思路,在处理完第一条电梯个数输入之后返回给main函数,并依此决定需要start的电梯。
  • 我的调度策略是将接收到的所有请求均分给启动的电梯,具体实现操作是对请求序号取模,根据余数分给不同电梯的托盘类等待处理。其余步骤与第一次作业几乎相同。
  • 首先将托盘Tray类实例化5次,以对应不同电梯的待处理请求列表(即使有的不一定用到);在输入线程InputHandler中依次均分给不同电梯;Elevator类在第一次作业的基础上增加一个maxnum属性,表示最大载客量,在上客过程中需要时刻关注载客量的变化,一旦满员,则不再上客。
  • 需要注意的是仍然需要先下客后上客,以提高载客人数与效率。
  • 本次调度性能一般,我在当初选定这个均分策略之前,尝试过电梯自己根据情况抢客的方法,最终还是由于对多线程的处理不到位造成很多bug,还是选择了均摊这种性能一般但正确性稳定的策略。

第三次作业

  • 在第二次作业的基础上将电梯分为三类,每类有不同的升降时间、最大载客人数和电梯的停靠楼层。并且输入中除了乘客搭乘请求之外,还新增了了增加特定电梯的请求。
  • 本次作业我没再按照以上两次作业的模式写,而是重新写了调度器类Controller,仍然新建InputHandler输入线程和Elevator电梯线程,新增电梯的请求则在InputHandler线程里开启新线程。
  • 我本次用到的换乘策略是:判断乘客请求的from和desti分别属于哪类电梯的停靠楼层,再将请求分解为from-中间楼层和                中间楼层-desti,这个换乘的中间楼层需要根据具体情况选择,比如from属于A类desti属于B类的请求,中间楼层可以根据距离选择1或15,B-C类可以选择1或5等等。
  • 若有新增电梯,则设置一个随机数,将请求均分到原电梯和新增同类电梯中。
  • 本次调度性能尚可,现在看来可以改进以下分摊请求这一块,不是简单均分,而是根据距离分到较近电梯的待处理请求里。

 

二、第三次作业可扩展性分析——以SOLID原则为例

  1. SRP单一功能原则:
    • 将电梯线程、生产者线程、调度器线程功能分开,各司其职,基本满足单一功能原则;
    • 生产者线程仅负责读入乘客请求和新增请求并添加至对应全局队列;
    • 电梯线程负责从调度器取得请求并运行请求;
    • 调度器线程负责依据请求来分发至不同电梯请求队列;
    • 如要对第三次作业继续进行扩展设计,那么这几个类的主要功能代码依然可以复用,因此这一设计是可扩展的。
  2. OCP开闭原则:LSP里氏替换原则:未使用继承。
    • 前两次作业的调度职责主要由电梯负责,第三次作业分出了对应的调度器线程,这部分工作几乎是从头做起的,可见前两次作业中没有满足这一原则的要求,仍然面临了重构的问题。
    • 若要对第三次作业进行复用,可能需要对调度器的调度算法进行改进,除此之外应该不会存在大的改动,也是可以扩展的。
  3. LSP里氏替换原则:未使用继承。
  4. ISP接口隔离原则:未使用接口。
  5. DIP依赖反转原则:未使用抽象类。

 

三、度量分析

第一次作业

  • UML类图

      BUAAOO_UnitTwo_第1张图片

  • 复杂度分析

      BUAAOO_UnitTwo_第2张图片

      BUAAOO_UnitTwo_第3张图片

      BUAAOO_UnitTwo_第4张图片

  • 优缺点点评:
    • 优点:各个类的分工较为明确;
    • 缺点:可以看出controller类和电梯类的复杂度过高,本次作业中电梯确实承担了过多的指责,而没有完全发挥调度的作用。
  • UML时序图

BUAAOO_UnitTwo_第5张图片

 

 

第二次作业

  • UML类图

BUAAOO_UnitTwo_第6张图片

 

  • 复杂度分析

BUAAOO_UnitTwo_第7张图片

BUAAOO_UnitTwo_第8张图片

BUAAOO_UnitTwo_第9张图片

  • 优缺点
    • 基本上复用了上一次的代码,做到了程序的可扩展;
    • 电梯类仍然承担着过多职责,因此复杂度很高;
    • 由于实行分摊策略,因此需要在输入线程中将请求加至不同托盘里,因此也有复杂度过高问题。
  • UML时序图

 BUAAOO_UnitTwo_第10张图片

第三次作业

  • UML类图

BUAAOO_UnitTwo_第11张图片

 

  • 复杂度分析

BUAAOO_UnitTwo_第12张图片

BUAAOO_UnitTwo_第13张图片

BUAAOO_UnitTwo_第14张图片

  • 优缺点:
    • 真正做到了调度器指责与电梯职责的分开;
    • 输入线程中由于还增加了新增电梯的请求,因此复杂度比较高;
    • 调度器和电梯线程都存在复杂度过高的问题,想来是我的很多功能的实现出现了代码重复、职责分区不明等问题。
  • UML时序图

BUAAOO_UnitTwo_第15张图片

 

四、Bug分析

  • 第一次作业强测与互测均未出现bug;本地刚开始中测时,出现过WA和超时等问题,分析后发现是自己对于多线程理解不到位,导致一些wait和notify命令用错,均已改正。
  • 第二次作业强测与互测均未出现bug。
  • 第三次作业强测和互测中都出现了玄学的WA,提示是乘客State问题,本地进行多次测试都未复现,无奈之下一字不改交上去debug,又都AC了,经了解不少同学也都有这样的情况,想来多线程确实容易出现线程不安全的情况,而我的程序在某些情况下可能会出现不稳定的运行结果。

五、Hack策略

  • 第一次作业未能成功hack;
  • 第二次作业hack到两位同学WA和RTLE问题,第一位同学的WA报错和我之后遇到的情况很类似,恐怕也是线程运行结果不稳定导致的,第二位同学的RTLE可能与调度策略有关,在极端情况下可能会超时。
  • 第三次作业未能成功hack;
  • 多线程作业的调试难度很大,我在本单元的hack中主要采用的是手动构造测试数据集的方法。

六、心得与体会

  本单元的电梯调度作业让我对多线程相关知识有了较为深入的了解。刚写第一次作业时觉得很无从下手,主要是没能很快的把刚学的多线程知识立马运用到实际应用中去,所以导致了第一次作业设计和架构上的不完善(例如没能实现完整的调度器功能),第二次作业时其实完全有时间完善一下第一次作业的构造,但发现均摊思想几乎不用动之前的构造时,还是摸鱼了(下场就是扬了性能分)。第三次作业由于电梯种类发生变化,这才不得不写出了完整的调度。每一单元的第一次作业还是尽量要完善自己的代码做到功能分开,并留出可扩展的空间。

       我对线程安全的理解是:当多个线程访问共享变量时,要通过对象锁和wait、notify方法做到一个对象在同步块内只能被一个线程访问;此外还要避免轮询问题,防止出现CPU超时。

       感谢讨论区同学慷慨分享的知识与见解,也感谢研讨课上不少同学分享出的专题研讨,这些都给了我不少的启发和收获。

 

你可能感兴趣的:(BUAAOO_UnitTwo)