BUAA_2022_OO_Unit2总结

OO Unit2总结

题目概述

本单元作业任务为实现一个模拟多线程的电梯调度系统。

HW5为迭代的基础:多部纵向非换乘电梯。具体为基于一个类似北京航空航天大学新主楼的大楼,大楼有 A,B,C,D,E五个座,每个楼座有对应的一台电梯,可以在楼座内 1-10 层之间运行。系统从标准输入中输入请求信息,程序进行接收和处理,模拟电梯运行,将必要的运行信息通过输出接口进行实时输出。电梯系统具有的功能为:上下行,开关门,以及模拟乘客的进出。

HW6增加了自增设横向与纵向电梯的功能,但保证所有请求不用换乘。

HW7增加了需要处理换乘的请求。

架构分析

HW5

  • 架构分析

    • 线程:架构中有电梯运行线程,调度器线程,输入线程三个线程

    • 总体架构采用了双生产者消费者模型

      • 生产者消费者模型:

        1、生产者为输入线程,消费者为调度器。共享队列:总请求的等待队列WaitQueue

        2、生产者为调度器,消费者为电梯。共享队列:每个电梯的的等待队列RequestQueue与每栋楼每一层对应的共享队列FloorQueue

  • 调度策略分析

采用look策略,在运行方向上,每运行到一层判断该层的等待队列,允许同方向的请求进入电梯,如果电梯内无请求运行方向上电梯外楼层中无请求,则反转方向,若该电梯的等待队列为空且输入已经停止,则线程结束。

  • UML图

 

 

  • 复杂性分析

    • 类复杂度比较,可以看出来Elevator的的复杂性有点过高了,把过多的Elevator方法都放在了该类中,且对于look策略采取了较为笨拙的方法完成,导致Elevator复杂度过高。

       

       

       

    • 方法复杂度分析:

    MethodCogCev(G)iv(G)v(G)
    Controller.Controller(RequestQueue, CopyOnWriteArrayList<RequestQueue>, CopyOnWriteArrayList<FloorQueue>) 0 1 1 1
    Controller.run() 15 4 10 11
    Elevator.Elevator(int, RequestQueue, FloorQueue, int, int) 0 1 1 1
    Elevator.canIn() 7 4 2 8
    Elevator.canOut() 3 3 2 3
    Elevator.close() 0 1 1 1
    Elevator.getDirection() 151 7 32 44
    Elevator.getName1() 0 1 1 1
    Elevator.in() 8 3 3 8
    Elevator.move() 1 1 2 2
    Elevator.open() 0 1 1 1
    Elevator.out() 3 1 3 3
    Elevator.run() 21 3 11 12
    FloorQueue.FloorQueue() 1 1 2 2
    FloorQueue.addFloorQueue(PersonRequest) 0 1 1 1
    FloorQueue.getQueue(int) 0 1 1 1
    FloorQueue.isEmpty() 3 1 2 3
    FloorQueue.removeFloor(int, PersonRequest) 0 1 1 1
    FloorQueue.size() 3 1 3 3
    Input.Input(RequestQueue) 0 1 1 1
    Input.run() 7 3 4 4
    Main.main(String[]) 1 1 2 2
    RequestQueue.RequestQueue() 0 1 1 1
    RequestQueue.add(PersonRequest) 0 1 1 1
    RequestQueue.getFirst() 5 2 4 5
    RequestQueue.getOne() 0 1 1 1
    RequestQueue.getQueue() 0 1 1 1
    RequestQueue.isEmpty() 0 1 1 1
    RequestQueue.isEnd() 0 1 1 1
    RequestQueue.remove(PersonRequest) 0 1 1 1
    RequestQueue.setEnd(boolean) 0 1 1 1

 

HW6

  • 架构设计

    本次作业在作业5的基础上增加了添加电梯指令与横向电梯。

    仍然采用双重生产者与消费者模型,在第一个生产者与消费者模型中,为调度器的策略增加了AddElevator的调度处理办法,即一部分新的Elevator线程在调度器中开始运行。同时,对于横向电梯的共享队列与纵向电梯类似,分为每一层的RequestQueue与每层相应楼座的BuildingQueue,而这些与纵向电梯的内容一起在第二个生产者消费者模型的托盘之中

    • 生产者消费者模型

    • InputThread && Controller

      • Shared queue:WaitQueue(PersonRequest) Elevators(EleRequest)

    • Controller && Elevator(VerticalElevatro || HorizontalElevator)

      • Shared queue:RequestQueue FloorQueue BuildingQueue

  • 调度策略

横向电梯与纵向电梯同样采用look策略。特别的,对于横向电梯而言:规定由A到

E的方向为正向,通过模运算判断运动方向

direction = (des - nowBuilding + 5) % 5;
  • MUL图

  •  

     

  • 复杂度分析

    • 类复杂度分析

      image-20220428215316144

    • 方法复杂度分析

      MethodCogCev(G)iv(G)v(G)
      Controller.Controller(RequestQueue, CopyOnWriteArrayList<PersonReqQueue>, CopyOnWriteArrayList<PersonReqQueue>, CopyOnWriteArrayList<FloorQueue>, CopyOnWriteArrayList<FloorQueue>, CopyOnWriteArrayList<EleVertical>, CopyOnWriteArrayList<EleHorizontal>) 0 1 1 1
      Controller.run() 44 4 25 26
      EleHorizontal.EleHorizontal(char, PersonReqQueue, FloorQueue, int, int, int) 0 1 1 1
      EleHorizontal.canIn() 11 4 2 10
      EleHorizontal.canOut() 3 3 2 3
      EleHorizontal.close() 0 1 1 1
      EleHorizontal.getBuilding() 0 1 1 1
      EleHorizontal.getDirection() 169 21 32 48
      EleHorizontal.in(PersonRequest) 0 1 1 1
      EleHorizontal.move() 8 1 2 5
      EleHorizontal.open() 0 1 1 1
      EleHorizontal.out() 3 1 3 3
      EleHorizontal.run() 33 6 16 18
      EleVertical.EleVertical(int, PersonReqQueue, FloorQueue, int, int, char) 0 1 1 1
      EleVertical.canIn() 7 4 2 8
      EleVertical.canOut() 3 3 2 3
      EleVertical.close() 0 1 1 1
      EleVertical.getDirection() 151 7 32 44
      EleVertical.getName1() 0 1 1 1
      EleVertical.in(PersonRequest) 0 1 1 1
      EleVertical.move() 1 1 2 2
      EleVertical.open() 0 1 1 1
      EleVertical.out() 3 1 3 3
      EleVertical.run() 34 6 17 18
      ElevatorReqQueue.ElevatorReqQueue() 0 1 1 1
      ElevatorReqQueue.add(ElevatorRequest) 0 1 1 1
      ElevatorReqQueue.getOne() 0 1 1 1
      ElevatorReqQueue.isEmpty() 0 1 1 1
      ElevatorReqQueue.remove(ElevatorRequest) 0 1 1 1
      ElevatorReqQueue.setEnd(boolean) 0 1 1 1
      FloorQueue.FloorQueue() 1 1 2 2
      FloorQueue.addFloorQueue(PersonRequest) 2 1 2 2
      FloorQueue.getQueue(int) 0 1 1 1
      FloorQueue.isEmpty() 3 1 2 3
      FloorQueue.removeFloor(int, PersonRequest) 0 1 1 1
      FloorQueue.size() 3 1 3 3
      Input.Input(RequestQueue, ElevatorReqQueue, PersonReqQueue) 0 1 1 1
      Input.run() 11 3 6 6
      Main.main(String[]) 2 1 3 3
      PersonReqQueue.PersonReqQueue() 0 1 1 1
      PersonReqQueue.add(PersonRequest) 0 1 1 1
      PersonReqQueue.getFirst() 5 2 4 5
      PersonReqQueue.getOne() 0 1 1 1
      PersonReqQueue.getQueue() 0 1 1 1
      PersonReqQueue.isEmpty() 0 1 1 1
      PersonReqQueue.isEnd() 0 1 1 1
      PersonReqQueue.remove(PersonRequest) 0 1 1 1
      PersonReqQueue.setEnd(boolean) 0 1 1 1
      RequestQueue.RequestQueue() 0 1 1 1
      RequestQueue.add(Request) 0 1 1 1
      RequestQueue.getFirst() 5 2 4 5
      RequestQueue.getOne() 0 1 1 1
      RequestQueue.getQueue() 0 1 1 1
      RequestQueue.isEmpty() 0 1 1 1
      RequestQueue.isEnd() 0 1 1 1
      RequestQueue.remove(Request) 0 1 1 1
      RequestQueue.setEnd(boolean) 0 1 1 1
      SafeOutput.println(String) 0 1 1 1

HW7

  • 架构设计

    本次作业在第六次作业的基础上增加了需要换乘电梯才可完成的请求。

    采用了流水线模式:

    //流水线部分
    |-Person:               :请求的拆分与封装
    |-Controller             :单例控制器
        |-EleHorizontal     :横向电梯
        |-EleVertical       :纵向电梯
        |-PerRequestQueue   :共享请求队列
        |-FloorQueue       :共享请求队列
    |-RequestCounter         :请求的回收
    |-SafeOutput             :输出

    Controller为单例模式的控制器,方便在流水线模式的各个阶段进行解析与调度。同时,Controller将请求放入相应的请求队列当中,不断更新电梯需要处理的请求队列。

    为了处理换乘的请求,同一个请求被分成三部分(或两部分)进入请求队列中,而同一个请求如何变成封装在一起的两个请求,成为一个Worklist呢,我在与同学的交流中得到启发建立了一个新的类Person,其中包括拆分后的Wrklist与Personid。而Request到Person的转化我放在了Controller中完成,等待队列的建立也是关于Person类的队列了。

  • 调度策略

    电梯类仍采用look策略,几乎没有变动。在速度,停靠楼层等细节稍作改动。其中,我把停靠楼层用数组标记的方法处理,便于在look策略中进行判断

  • MUL图

     

     

  • 复杂度分析

     

     

MethodCogCev(G)iv(G)v(G)
Controller.Controller(RequestQueue, CopyOnWriteArrayList<RequestQueue>, CopyOnWriteArrayList<FloorQueue>) 0 1 1 1
Controller.run() 15 4 10 11
Elevator.Elevator(int, RequestQueue, FloorQueue, int, int) 0 1 1 1
Elevator.canIn() 7 4 2 8
Elevator.canOut() 3 3 2 3
Elevator.close() 0 1 1 1
Elevator.getDirection() 151 7 32 44
Elevator.getName1() 0 1 1 1
Elevator.in() 8 3 3 8
Elevator.move() 1 1 2 2
Elevator.open() 0 1 1 1
Elevator.out() 3 1 3 3
Elevator.run() 21 3 11 12
FloorQueue.FloorQueue() 1 1 2 2
FloorQueue.addFloorQueue(PersonRequest) 0 1 1 1
FloorQueue.getQueue(int) 0 1 1 1
FloorQueue.isEmpty() 3 1 2 3
FloorQueue.removeFloor(int, PersonRequest) 0 1 1 1
FloorQueue.size() 3 1 3 3
Input.Input(RequestQueue) 0 1 1 1
Input.run() 7 3 4 4
Main.main(String[]) 1 1 2 2
RequestQueue.RequestQueue() 0 1 1 1
RequestQueue.add(PersonRequest) 0 1 1 1
RequestQueue.getFirst() 5 2 4 5
RequestQueue.getOne() 0 1 1 1
RequestQueue.getQueue() 0 1 1 1
RequestQueue.isEmpty() 0 1 1 1
RequestQueue.isEnd() 0 1 1 1
RequestQueue.remove(PersonRequest) 0 1 1 1
RequestQueue.setEnd(boolean) 0 1 1 1

MUL协作图

前两次作业:

 

 第三次作业协作图:

 

 

同步块与锁的分析

在用到共享队列的地方都通过synchronized加了锁,同时在共享对象类内对自身属性读写的方法也都加了锁

  • 线程中涉及的对共享队列的连续的读、写、删除的操作时,通过同步块的方式保证操作的连贯性与完整性

    //In Elevator
           synchronized (floorQueue) {
               Iterator<Person> item = floorQueue.getQueue(curFloor).iterator();
               while (item.hasNext()) {
                   Person req1 = item.next();
                   int tempDirection = req1.getFirst().getToFloor() - curFloor;
                   if (((tempDirection > 0) && (direction > 0)) ||
                          ((tempDirection < 0) && (direction < 0)) || direction == 0) {  //同向
                       req = req1;
                       break;
                  }
              }
               floorQueue.removeFloor(curFloor, req);
          }
  • 共享对象的同步方法

    public synchronized CopyOnWriteArrayList<Person> getQueue(int floor) {
           this.notifyAll();
           return floorQueue.get(floor); //from 1
      }
       
       public synchronized void removeFloor(int floor, Person req) {
           floorQueue.get(floor).remove(req);
           this.notifyAll();
      }

     

Bug策略

  • HW5

    bug集中在线程安全以及轮询上,在中测出现CTLE,经过分析是进程在没有请求的之后仍然在空转的情况下,出现了过度消耗CPU资源的问题。

    在强测中出现RTLE的情况,调度策略出现问题导致电梯在中间过程中停下来。

  • HW6

    自己的bug仍然出现在调度策略和线程安全的问题,在电梯无人时,与运动方向相反的方向有新的请求时出现停下的错误。导致强测出现RTLE的点。对于同一楼层多部电梯处理多条请求我采用自由竞争的策略,但是没有对共享队列成功加锁,导致同一个人进入两个电梯的情况发生。

    对别人程序的hack:

    • 构造同一栋楼多个电梯同时处理同一层的多个请求的数据,可以hack线程不安全的情况。

  • HW7

    第七次作业没有处理相同Floor但是需要换乘的情况,强侧出现了RTLE。

    Hack:

    • 由于hack开始之后很快发现了自己的这个bug,然后针对这个bug创建数据hack了同屋的小伙伴。

    • 构造多个同层同电梯换乘的请求,hack线程不安全的bug

    • 构造多个一层的请求,hack电梯添加与调用第一层电梯的bug

心得体会

本单元第一次作业的线程安全问题的处理在当时的一周初步接触多线程带来了较大的困难,在研究锁、同步块…的过程中一边学习一边摸索,整体的生产者消费者设计架构可以在实验中获得很大的启发。在研究实验代码的过程中逐渐理解多线程的原理。

总体而言难度相比第一单元简单一些(有实验代码的启发),迭代开发的难度也不会很大,电梯的策略与调度的策略一旦构思好,可以较为顺畅的写出来,后续debug大都面向线程安全与调度策略进行测试,debug的难度也比第一单元有所降低。

个人问题是中测截止前没有认真debug,对测试数据的构造没有多加分析与考虑,简单的测试便放心的提交,导致强测出现低级的bug,下一个单元要更加认真继续努力↖(^ω^)↗

 
posted @ 2022-04-29 16:51  qiaoqiaqiq  阅读(32)  评论(1编辑  收藏  举报