oo第二单元总结

第二单元作业总结

第一次作业

由于没有通过中测,故不献丑了

第二次作业

同步块的设置和锁的选择

  • 共享对象

    由图可知,building对象被输入线程,电梯线程,调度器线程共享;commingqueue对象被输入线程和调度器线程共享。

  • 同步块设置

    • 输入线程

      仅当需要访问某对象时将之加锁。需要唤醒调度器中对commingqueue的等待。

    • 电梯线程

      每次访问building时加锁。对于电梯的终止条件,没有通过commingqueue来实现,而是在调度器里面对building设置标志,电梯访问该标志终止。

      电梯没有任务时,等待调度器分配任务给processqueue。

    • 调度器线程

      这里只有一个输入源,故按照逻辑先看commingqueue是否为空,若为空在看输入是否结束,若结束则唤醒所有等待调度的电梯线程,否则调度器等待输入,将commingqueue释放。因此先锁commingqueue再锁building。

      但正因为这样的逻辑,导致无法支持换乘的二次调度,因为换乘请求可能还未加入commingqueue,而输入已经结束且commingqueue为空导致电梯提前终止。因此下一次作业我采取假换乘,即不把请求加给commingqueue,而是直接加到processqueue中。

      其实还有一种解法,将电梯监视器也被调度器共享,增加调度终止逻辑可以实现,但同时会导致共享对象的增多。

调度器设计

调度器访问commingqueue,把其中的请求按照一定策略分发给building中的processqueue,并通过commingqueue的end标志和empty标志给电梯的processqueue的end标志置位表示调度结束。这里的策略是分给当前最懒的电梯以使得所有电梯都能够运行起来。

Bug

  • debug

    本次作业模仿实验课代码的模板写成,公测没有遇到什么bug,强侧也没有被hack。

  • hack

    hack时尝试在输入一堆数据后就输入null,测试有无睡死过去的进程。

思考与总结

  • 性能分析

    • 分析

      既支持调度器调度电梯,也支持电梯之间互相抢夺指令。

      本次作业调度器使得所有电梯能够动起来,没有特别的调度策略,也没有动态根据电梯情况调度。

      电梯采取不完全自由竞争,即电梯只能够在运行主请求的过程中可以去抢夺请求和捎带已经被调度的请求。这样不完全符合实际,性能上略微损失。

    • 改进

      可以去除每一个电梯自己的运行队列和调度器,电梯没有指令时仅仅去向公共的请求队列要一个指令,如此更加符合实际且消除电梯抢夺请求时维护processqueue的花销。

  • 线程安全

    commingqueue不需要被电梯共享,通过building这一共享对象实现scheduler和elevator之间的通信。降低线程之间共享对象的数量。

  • 层次化设计

    • 分析

      电梯监视器这个类很合适,将电梯的人的请求单独管理,降低耦合度。

      难以支持换乘的需求,因为elevator无法访问commingqueue。因为共享对象存在耦合,粒度太大,难以支持更加细致的功能。

    • 改进
      • 方案一

        完全自由竞争,即集中式设计。去除processqueue,building作为commingqueue的容器,去除调度器。

      • 方案二

        降低抢夺的自由度,损失性能,每个电梯只能抢夺和捎带自己的请求,即分布式设计。building作为commingqueue的容器供调度器使用,每个电梯情况被调度器共享,方便调度。电梯线程共享commingqueue,以便结束。

第三次作业

同步块的设置和锁的选择

  • 共享对象

    同第二次作业,building对象被输入线程,电梯线程,调度器线程共享;commingqueue对象被输入线程和调度器线程共享。

  • 同步块设置

    • 输入线程

      输入运行模式时锁住building,这里事实上不需要唤醒building,因为没有building的等待。

      输入结束时给commingqueue的end置位,来一个请求就锁住commingqueue并加入,来一个电梯请求就锁住building并加入。

    • 调度器线程

      加锁情况和第二次作业一致。

    • 电梯线程

      与第二次作业的区别在于电梯的终止条件是当前电梯内没有人且所有电梯的processqueue都为空且调度结束才停止运行,因为存在换乘的可能电梯无法根据自己的processqueue为空且调度结束就停止运行。

      其实主要是由于本人架构设计的模糊不清导致的结构混乱,理论上换乘是要把请求再次加入commingqueue进行二次调度,但这里由于共享对象耦合,采用当请求出电梯时直接将请求加入processqueue的方式模拟二次调度。

调度器设计

调度器的调度策略是按照起始楼层进行分类,分成三类:1-3,4-17,18-20。具体策略参见代码,值得一提的是最后有一个是否分配给A的调度判断以防止B、C太过繁忙,而A无事可做。

private String mayGiveItToA(String type) {
       String newtype = type;
       if (type.equals("C")) {
           synchronized (this.building) {
               if (this.building.getNumOfProcessInType("C") >
                       2 * this.building.getNumOfProcessInType("A")) {
                   newtype = "A";
              }
          }
      }
       else if (type.equals("B")) {
           synchronized (this.building) {
               if (this.building.getNumOfProcessInType("B") * 2 >
                       3 * this.building.getNumOfProcessInType("A")) {
                   newtype = "A";
              }
          }
      }
       return newtype;
  }

   public static String selectFromLowFloor(int from, int to) {
       String type;
       if ((to >= 1 && to <= 3) || (to >= 18 && to <= 20)) {
           type = "C"; //C直送
      }
       else {
           if ((from == 3) || (from == 1)) {
               type = "B";
          }
           else {
               type = "C";
          }
      }
       return type;
  }

   public static String selectFromHighFloor(int from, int to) {
       String type;
       if ((to >= 1 && to <= 3) || (to >= 18 && to <= 20)) {
           type = "C"; //C直送
      }
       else {
           if (from == 18) {
               type = "A";
          }
           else {
               type = "C";
          }
      }
       return type;
  }

   public static String selectFromMiddleFloor(int from, int to) {
       String type;
       if (from % 2 == 1) {
           if (to % 2 == 1) {
               type = "B"; //奇数到奇数
          }
           else {
               if ((to == from - 1) || (to == from + 1)) {
                   type = "A";
              }
               else {
                   type = "B";
              }
          }
      }
       else {
           type = "A"; //偶数只能A
      }
       return type;
  }

   private String selectAType(PersonRequest request) {
       int from = request.getFromFloor();
       int to = request.getToFloor();
       String type;
       if (from >= 1 && from <= 3) {
           type = selectFromLowFloor(from, to);
      }
       else if (from >= 18 && from <= 20) {
           type = selectFromHighFloor(from, to);
      }
       else {
           type = selectFromMiddleFloor(from, to);
      }
       type = this.mayGiveItToA(type);
       return type;
  }

Bug

  • bug

    在写的过程中想过通过将请求重新投放到commingqueue实现,但由于结构紊乱和冗余导致死锁,因此采用请求出电梯时进行二次分配,避免死锁,但这其实不算是很正当的解法。

  • hack

    通过卡提前终止输入来测试是否存在死等。

架构设计

  • 类图

    由于类之间没有用到继承和接口,类之间关系简单,故直接采用idea的插件生成。

  • 类说明

    Requestqueue

    作为输入请求的容器存在,封装管理队列的方法。

    RequestOnFloor

    将请求按照楼层分类的类。

    Building

    作为电梯的运行队列的容器存在,内置运行队列的2种组织形式:按照楼层分类,按照电梯id分类。该类能够看到所有电梯的运行队列,这是出于自由竞争捎带的考虑,但与调度器之间存在矛盾,对可扩展性带来麻烦。

    Scheduler

    线程类。将commingqueue的请求分配给不同的电梯,在本次作业的需求下和自由竞争捎带的基础上,该调度器只保证所有电梯都能运行。同时在请求被分配完成且输入结束(为换乘需求挖坑)后对building中所有的运行队列添加结束信号,以便电梯线程停止运行。

    Elevatormonitor

    出于降低电梯类复杂度而设计,包含电梯里人的请求队列和将其按照目的地分类的map,并封装同步管理该队列和map的方法。

    Elevator

    线程类。包含电梯的属性参数和运行方法,内置Elevatormonitor,building对象,便于进行捎带时对其他电梯里processqueue中的删除操作以及对电梯里面请求的加入或删除。

    InputThread

    线程类。专门处理输入的线程,将输入的请求放入commingqueue对象中。

  • 协作图

 

思考与总结

  • 线程安全

    半调度半抢夺的设计导致结构紊乱和冗余,可操作空间小,容易产生死锁,切忌在共享对象具有独立性时为了统一管理而使用共享对象的组合。

  • 层次化设计

    • 特点

      半自由竞争加半调度。每个电梯拥有自己的processqueue,也有调度器去分配请求,每个电梯也能去抢夺其他电梯的请求。

    • 不足

      实现捎带时不完全符合实际情景,电梯捎带对象只能是已经被调度器分配的请求,因为building里面的请求是在被请求被分配是才加入,而不是请求输入时就被加入。

      共享对象之间存在耦合(processqueue组合在building中),操作难度大,可拓展性差,容易产生死锁。

    • 改进
      • 方案一

        采用电梯自由竞争的策略,放弃对功耗的考虑(逃),即采用集中式设计。

      • 方案二

        采用分布式设计。将所有的电梯监视器放在一个容器里面,被调度器共享,从而可以支持换乘的终止条件,也可以实现更精细的调度。

posted @ 2021-04-23 22:47  depool  阅读(39)  评论(0编辑  收藏  举报