OO第二单元总结

第二单元体验感总体要比第一单元好上不少,总算能赶上进度不至于太赶。

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

本单元主要用到“生产者-消费者”模型。在作业中,对控制器中的主从请求的添加与获取采用了 synchronized 关键字加锁。

通过设置flg控制开始与结束,电梯调度算法方面,采用了look算法,并进行了一定的优化,通过使用wait()和notifyAll(),电梯在空闲时不会无脑疯狂调转方向,从而更贴近真实情况。Look算法据我的理解是一种分治算法,具体说来先判断电梯有无人:如果有人判断是否需要开门,不需要则继续沿原方向行进;如果没有人则进一步判断有没有请求,若没有请求则原地等待,若有请求则按原方向行进。

电梯对同步块的访问基本都是修改型的,例如“添加 request”,“获取 request”,如果不加锁,可能就会造成资源的共享导致错误(例如一个人同时进了两个电梯)。

public void run() {
      String pattern = elevatorInput.getArrivingPattern();
      scheduler.setPattern(pattern);
      PersonRequest personRequest;
      Request request;
      MyRequest myRequest;
      while ((request = elevatorInput.nextRequest()) != null) {
          String reqStr = request.toString();
          int index = reqStr.indexOf("ADD-");
          if (index == 0) {
              int lastIndex = reqStr.lastIndexOf("-");
              String id = reqStr.substring(4, lastIndex);
              //System.out.println(id);
              String type = reqStr.substring(lastIndex + 1);
              //System.out.println(type);
              //System.out.println("新增电梯-"+id+"-"+type);
              Elevator elevator;
              // 增加新的电梯
              switch (type) {
                  case "A":
                      elevator = new Elevator(id, 8, 600, floorA, scheduler);
                      elevator.start();
                      break;
                  case "B":
                      elevator = new Elevator(id, 8, 600, floorB, scheduler);
                      elevator.start();
                      break;
                  case "C":
                      elevator = new Elevator(id, 8, 600, floorC, scheduler);
                      elevator.start();
                      break;
                  default:
                      break;
              }
              continue;
          }
          personRequest = (PersonRequest) request;
          //System.out.println(personRequest);
          myRequest = new MyRequest(personRequest.getFromFloor(),
                  personRequest.getToFloor(),
                  personRequest.getPersonId());
          scheduler.putRequest(myRequest);
      }
      scheduler.setFinished();

      try {
          elevatorInput.close();
      } catch (Exception e) {
          e.printStackTrace();
      }
  }
}

二、调度器设计

调度器负责接受从输入线程传来的请求,将其放入请求列表。电梯通过暴力循环请求列表竞争任务,获取或者完成任务后更新请求列表。第二次作业开始调度器单独作为一个线程,根据我的设计,调度器与输入线程共享总等待队列的对象,调度器与每个电梯共享电梯自己的等待队列的对象,即调度器是输入线程和电梯线程之间的桥梁,从输入线程接收到乘客的需求,然后分配到各个电梯进行处理,这样看来,电梯就只需要根据自己的等待队列进行运行,调度器的任务就是根据总等待队列和每个电梯的等待队列综合分配乘客到各个电梯,具体的调度方法也很简单很暴力:算出所有电梯的等待队列的人数和总等待队列的人数和,进而算出平均人数,然后把总等待队列中的乘客分配到每个电梯中使得每个电梯中的等待队列的人数达到平均人数。

//  检查移动方向
private void checkDirection() {
  if (currentPersonNum == 0 && !scheduler.isInRightDirection(
          floor, currentFloor, currentDirection)) {
      currentDirection = -currentDirection;
      // System.out.println(String.format(
      // "%s: Direction turned at floor %d, now go %s",
      // elevatorId, currentFloor,
      // (currentDirection == 1) ? "up" : "down"));
  }
}

三、第三次作业架构与可扩展性

我的第三次作业主要分为了电梯、楼层,处理器,请求解析器等类,类的结构关系见 UML 图。

我的架构设计重心主要放在了功能方面,对于性能没有过多设计(所谓的协作关系只是保证线程安全性,在这个基础上采用电梯间相互争抢的模式,即谁先到达某个请求的出发楼层,谁就服务这个请求。这种策略简单暴力,调度器不需要分配请求,而效率最后被证明也很好)。

这次作业的可扩展性有进步的地方,也有相当大的不足。对于电梯这个实体,我在这次作业中通过Floor作为电梯的构造参数之一,管理电梯的楼层信息,包括可达楼层、最高最低层、判断某个楼层是否可达。包括Floor在内的所有电梯属性都可以在主类中显式地修改,避免硬编码,降低耦合度。可扩展性不太好的方面主要包括调度策略难以实现多样化,并且难以从自由竞争修改成分派调度。

 

UML类图:

 

UML协作图:(由于主类不是线程,其仅负责启动上述线程而后就结束了,故未在图中体现)

四、分析自己程序的bug

第一次作业被hack出一个CTLE的bug,原因是因为使用了暴力轮询来使电梯获取调度器请求队列内的请求。后来在请求队列没有请求的时候 wait() 一下就好了。反思:多看讨论区的讨论,在de这个bug之前我都不知道暴力轮询是啥。

第二次作业是性能分最优的一次(感天动地),在互测中被hack出错误,在捎带的过程中若是泛起最底层或最顶层有跨越(停留电梯数目*7+2)个数的请求, 那么会发生死锁.。后来发现删除优化的那一行代码, 而且添加up位在最底层与最顶层的改变, 就可以修复bug。

第三次作业则有点”惨不忍睹“,强测有三个点没过。现在正在修改中,目测为电梯在不应该的地方发生了死锁。

五、检测别人bug的方法

交了两个数据均未刀中(捂脸),吐槽一下多线程hack确实不容易,上单元学到的搭评测机的方法本单元没用上,导致互测时人整个是比较佛系的(×),尝试研读其他人的代码无果后放弃。

六、感想

本单元的学习相对比上单元友好,历次作业之间的跨度没有那么大,可扩展性也做得很好,总体来说通过本单元学习初识了多线程,还是很有收获的。对于我来说,输入推入请求,电梯取出请求,这种"生产者-消费者"模型的核心没有变。只要能设计好架构,让耦合度降到最低,那么任意电梯数量、各种限制要求都可以轻松地满足。调度算法也应该封装起来,想用哪种算法单独替换即可,不要因为算法而改变架构本身。

通过这个单元的训练,我对多线程编程有了深入的了解,掌握了线程间通信、同步、互斥的方法,保证线程的安全性。更重要的一方面是,设计架构的能力有了不小的提升,明白如何才能设计出高内聚、低耦合的程序。

 

posted @ 2021-04-26 00:38  Charlie_Cosmos  阅读(48)  评论(1编辑  收藏  举报