OO Unit2多线程电梯总结博客

OO Unit2多线程电梯总结博客

传说中的电梯居然就这样写完了~撒花?给今年的课程组笔芯,提供了输入输出接口包,不用像以前一样麻烦地处理输入,改善了面向过程和面向Wrong Format的程序设计。

 

程序设计思路和度量分析

三次作业都贯穿着一对函数:

Crawl & Slide

我的所有的程序一个中心,两个基本点,就是每次调度器会从队列里拿出一个“主请求”,如果这个主请求是向上走的,那么使用Crawl(int from, int to)函数,如果是向下走的,则使用Slide(int from , int to )函数;其中fromto是这个主请求的出发楼层和到达楼层。Crawl和Slide是贯穿始终的两个基本点。

在这两个函数中,使用一个循环,每到达一个楼层之前,Sleep对应的运行时间,然后将电梯属性onThisFloor(int floor)设置到这个楼层(第二第三次的电梯还要输出“ARRIVE”,不可以直接Sleep一个很长的时间然后摸鱼)。此时先看看电梯里是不是有人,如果有人在这一层楼下,如果门是关着的,那么先开门,然后输出OUT;乘客出去之后,在遍历队列看看有无人在这一层楼上电梯,(同样若门是关着的,先开门)若有人下则输出IN。最后,若门的状态是打开的,则输出关门CLOSE操作。一定要避免重复输出开关门信息。

傻瓜调度 - FCFS

先来先服务,严格按照顺序,一次只能运送一个人,没有性能可言

类的设计 - 傻瓜调度电梯

五个类:

  • MainClass - 主函数,处理输入

  • Elevator - 只是一个电梯厢,不会自己跑,全靠调度器安排

  • Person - 保存有ID,出发楼层,目标楼层,以及访问这些变量的方法

  • Dispatch - 调度器,包含电梯厢,人,请求队列,只有一个run任务是要在特定时间输出信息的。

  • TaskQueue - 在LinkedBlockingQueue<>基础上运用了一些方法,取头元素,判断是否已满或为空等等

 

傻瓜电梯的Complexity Metrcis

分析结果中可以看到ev, iv, v这几栏,分别代指基本复杂度(Essential Complexity (ev(G))、模块设计复杂度(耦合度)(Module Design Complexity (iv(G)))、Cyclomatic Complexity (v(G))圈复杂度(独立路径的条数)。OCavgWMC两个项目,分别代表类的方法的平均循环复杂度和总循环复杂度。

Methodev(G)iv(G)v(G)
Dispatch.Dispatch(Elevator)111
Dispatch.NewRequest(PersonRequest)111
Dispatch.Work()122
Dispatch.run()122
Elevator.Elevator(int)111
Elevator.Run()111
Elevator.Stop()111
Elevator.isClosed()111
Elevator.isOpen()111
Elevator.isRunning()111
Elevator.onThisFloor(int)111
Elevator.onWhichFloor()111
MainClass.main(String[])333
Person.Person(int,int,int)111
Person.getDstFloor()111
Person.getId()111
Person.getSrcFloor()111
Person.goIn()111
Person.goOut()111
Person.isInElevator()111
TaskQueue.getQueue()111
TaskQueue.isEmpty()111
TaskQueue.isFull()111
TaskQueue.pushELm()111
TaskQueue.putIntoQueue(PersonRequest)111
ClassOCavgWMC
Dispatch1.255
Elevator18
MainClass33
Person17
TaskQueue15
傻瓜电梯的Dependency Metrcis

依赖度分析度量了类之间的依赖程度。有如下几种项目:

  1. Cyclic:指和类直接或间接相互依赖的类的数量。这样的相互依赖可能导致代码难以理解和测试。

  2. Dcy和Dcy*:计算了该类直接依赖的类的数量,带*表示包括了间接依赖的类。

  3. Dpt和Dpt*:计算了直接依赖该类的类的数量,带*表示包括了间接依赖的类。

ClassCyclicDcyDcy*DptDpt*
Dispatch03311
Elevator00022
MainClass02400
Person00012
TaskQueue00012

总的来说,傻瓜调度的电梯还是非常简单的,因为没有性能要求,我史无前例地(之后大概也不会)在强测中拿了100分。

 

捎带 - ALS

取出主请求,在上下运行的过程中如果有同一个方向的,然后到达了该楼层是电梯内某人的目的地,则下来,该楼层是upQueue或downQueue的其中一个人的始发地,则上车。

类的设计 - 可捎带电梯
  • MainClass - 主函数,处理输入

  • Elevator - 只是一个电梯厢,不会自己跑,全靠调度器安排

  • Person - 保存有ID,出发楼层,目标楼层,以及访问这些变量的方法和各种人的状态

  • Dispatch - 调度器,包含电梯厢,人,包含一个上行队列upQueue和一个下行队列downQueue,有最重要的crawl和slide方法,输出信息在Dispatch中完成

  • TaskQueue - 改成了以ArrayList为基础的队列,可在任意位置插入人,可以判断是否为空或满

类的设计与第一次作业基本保持一致,但是功能有一些变化,给调度器的职责更重了

(这个图画起来有一点专门为人服务的感觉哈哈哈啊)

ALS Complexity Metrcis
Methodev(G)iv(G)v(G)
Dispatch.Dispatch(Elevator)111
Dispatch.NewRequest(PersonRequest)133
Dispatch.canEnd()245
Dispatch.carryLeftPeople(Elevator,Person)133
Dispatch.closeDoor(Elevator,int)122
Dispatch.crawl(int,int)112021
Dispatch.getMain(Elevator,TaskQueue,TaskQueue)177
Dispatch.run()122
Dispatch.slide(int,int)112021
Dispatch.work()61414
Elevator.Close()111
Elevator.Elevator(int)111
Elevator.Open()111
Elevator.Run()111
Elevator.Stop()111
Elevator.getPerson(int)111
Elevator.goIn(Person)111
Elevator.goOut(Person)111
Elevator.hasPeople()111
Elevator.isClosed()111
Elevator.isGoDown()111
Elevator.isGoUp()111
Elevator.isOpen()111
Elevator.isRunning()111
Elevator.onThisFloor(int)111
Elevator.onWhichFloor()111
Elevator.peopleNum()111
Elevator.setGoDown()111
Elevator.setGoUp()111
MainClass.main(String[])355
Person.Person()111
Person.Person(int,int,int)113
Person.getDirection()111
Person.getDstFloor()111
Person.getId()111
Person.getSrcFloor()111
Person.goIn()111
Person.goOut()111
Person.isGoingDown()111
Person.isGoingUp()111
Person.isInElevator()111
TaskQueue.getQueueElm(int)111
TaskQueue.insertQueue(Person,int)999
TaskQueue.isEmpty()111
TaskQueue.isFull()111
TaskQueue.kickOut(Person)111
TaskQueue.pushELm()111
TaskQueue.putQueueHead(Person)111
TaskQueue.putQueueTail(Person)111
TaskQueue.size()111
ClassOCavgWMC
Dispatch5.555
Elevator119
MainClass55
Person1.1813
TaskQueue1.8917
ALS Dependency Metrcis
ClassCyclicDcyDcy*DptDpt*
Dispatch03311
Elevator01122
MainClass02400
Person00034
TaskQueue01112

 

 

多电梯 - SS

我的设计思路有以下几点:

  1. 先判断是否能直达,若能直达,Dispatch把任务放到对应电梯的队列里,优先分配A电梯,其次B、C。 若不能直达,则给出换乘方案:把两段任务都先直接放到放到相应的电梯队列里,但是第二个任务的Validity为False,即便电梯到了他的出发楼层,他也不能“进入电梯”。

  2. 通过计算,到距离这个人最近的换乘点进行换乘,到达换乘点后,若发现这个人是一个换乘的前半段任务,那么把第二个人的Validity设置为True,他就可以在Crawl和Slide中上电梯了。

  3. 每个电梯只能从自己的队列里取任务,不能关心外面的Dispatch和InputHandler,上下过程中可捎带。

  4. 所有输出信息都写在Elevator当中,剩下可以安心运行而不关心琐碎的事情,在for循环中Arrive,Crawl是向上运行,Slide是向下运行。进进出出,开门关门都是电梯的事情。

  5. 换乘人在第一阶段运行中,输入依然可以正确进行,只要不输入Command + D

  6. 如果主请求运输完毕后,电梯会先将被捎带还在电梯内的乘客送到目的地,然后再取新的请求。

类的设计 - 多电梯
  • MainClass - 主函数,启动一个输入线程和一个调度器线程

  • Elevator - 一个电梯厢,包含一个上行队列upQueue和一个下行队列downQueue,本身是一个Thread,包含所有的输出信息;调度器只负责往电梯的队列里塞人,电梯需要从自己的队列里取用需求,并且上下运动捎带,有最重要的crawl和slide方法

  • Person - 保存有ID,出发楼层,目标楼层,以及访问这些变量的方法和各种人的状态;

    增加了Ancestor和exchange变量,如果exchange为true,那么表明有换乘的“前人”,通过检测Ancestor的楼层状态可以判断这个人是否能够进入电梯

  • Dispatch - 调度器,包含电梯厢,人,主队列,启用三个电梯线程决定某人请求是否需要换乘,且应该放到哪个电梯的哪个队列里。

  • TaskQueue - 以ArrayList为基础的队列,可在任意位置插入人,可以判断是否为空或满

  • InputHandler - 单独开一个线程来处理输入,并将需求传递给Dispatch

 

SS Multielevator Complexity Metrcis
Methodev(G)iv(G)v(G)
multielevator.Dispatch.AllStop()234
multielevator.Dispatch.Dispatch(Elevator,Elevator,Elevator)111
multielevator.Dispatch.canStop()111
multielevator.Dispatch.nearExchangeab(int)517
multielevator.Dispatch.nearExchangeac(int)212
multielevator.Dispatch.nearExchangebc(int)8110
multielevator.Dispatch.putExchangePerson(Person,int,int,int,Elevator,Elevator)133
multielevator.Dispatch.putInPerson(Person)11414
multielevator.Dispatch.putStraight(Person,int,int)477
multielevator.Dispatch.reachA(int)213
multielevator.Dispatch.reachB(int)215
multielevator.Dispatch.reachC(int)212
multielevator.Dispatch.run()111
multielevator.Dispatch.straight(int,int)467
multielevator.Dispatch.work()111
multielevator.Elevator.Arrive(int)111
multielevator.Elevator.Close(int)122
multielevator.Elevator.Crawl(int,int)122022
multielevator.Elevator.Elevator(String,int,int)111
multielevator.Elevator.Open(int)122
multielevator.Elevator.PickUp(int,int)144
multielevator.Elevator.Run()111
multielevator.Elevator.Slide(int,int)122022
multielevator.Elevator.Stop()111
multielevator.Elevator.allEmpty()256
multielevator.Elevator.carryLeftPeople()133
multielevator.Elevator.getMain()177
multielevator.Elevator.getPerson(int)111
multielevator.Elevator.goIn(int,Person)222
multielevator.Elevator.goOut(int,Person)111
multielevator.Elevator.hasPeople()111
multielevator.Elevator.isClosed()111
multielevator.Elevator.isGoDown()111
multielevator.Elevator.isGoUp()111
multielevator.Elevator.isOpen()111
multielevator.Elevator.isRunning()111
multielevator.Elevator.onThisFloor(int)111
multielevator.Elevator.onWhichFloor()111
multielevator.Elevator.peopleNum()111
multielevator.Elevator.putInDown(Person)111
multielevator.Elevator.putInUp(Person)111
multielevator.Elevator.run()11717
multielevator.Elevator.setGoDown()111
multielevator.Elevator.setGoUp()111
multielevator.Elevator.setStopSignal()111
multielevator.InputHandler.InputHandler(Dispatch)111
multielevator.InputHandler.run()388
multielevator.MainClass.main(String[])111
multielevator.Person.ArriveDst()111
multielevator.Person.Person()111
multielevator.Person.Person(int,int,int)113
multielevator.Person.getDirection()111
multielevator.Person.getDstFloor()111
multielevator.Person.getId()111
multielevator.Person.getSrcFloor()111
multielevator.Person.goIn()111
multielevator.Person.goOut()111
multielevator.Person.hasArrivedFirst()323
multielevator.Person.isExchange()111
multielevator.Person.isGoingDown()111
multielevator.Person.isGoingUp()111
multielevator.Person.isInElevator()111
multielevator.Person.isOnThisFloor(int)111
multielevator.Person.needExchange(Person)111
multielevator.TaskQueue.getQueueElm(int)111
multielevator.TaskQueue.isEmpty()111
multielevator.TaskQueue.isFull()111
multielevator.TaskQueue.kickOut(Person)111
multielevator.TaskQueue.putQueueHead(Person)111
multielevator.TaskQueue.putQueueTail(Person)111
multielevator.TaskQueue.size()111
ClassOCavgWMC
multielevator.Dispatch3.5353
multielevator.Elevator2.4774
multielevator.InputHandler36
multielevator.MainClass11
multielevator.Person1.2520
multielevator.TaskQueue17

我把所有源文件放在了一个elevator包里。有一些方法复杂度还是居高不下,尤其是核心函数Crawl和Slide对人、楼层、队列的操作较多,导致了圈复杂度大。当总体上还是比较明了的。

 1 public void Crawl(int from, int to) throws InterruptedException {
 2          for (int i = from; i <= to; i++) {
 3              if (i == 0) { continue; } //没有第0层
 4              this.onThisFloor(i);
 5              if (i > from) {
 6                  Thread.sleep(this.runningTime);
 7                  this.Arrive(i); // OUTPUT
 8              }
 9              //先检查有无人下电梯
10              for (int k = 0; k < this.peopleNum() && this.hasPeople();) {
11                  Person p = this.getPerson(k);
12                  while (p.getDstFloor() == i) {
13                      // 放人下来
14                      this.Open(i);
15                      this.goOut(i,p); // 走出电梯
16                      p.ArriveDst(); // 人 p 到达楼层
17                      if (p.isExchange()) { p.hasArrivedFirst(); } // ancestor释放到达信号
18                      if (!this.hasPeople()
19                              || k >= this.peopleNum() - 1) {
20                          if (k == 0 && this.hasPeople()) {
21                              p = this.getPerson(0);
22                              continue;
23                          } else {
24                              break;
25                          }
26                      }
27                      p = this.getPerson(k);
28                  }
29                  k++;  // 继续看下一个人要不要出电梯
30              }
31              // 检查有无人上电梯
32              for (int j = 0; j < this.upQueue.size()
33                      && !this.upQueue.isEmpty(); ) {
34                  //System.out.println("Upqueue size is " + this.upQueue.size());
35                  Person p = this.upQueue.getQueueElm(j);
36                  while (p.getSrcFloor() == i && p.isGoingUp()
37                          && p.hasArrivedFirst()) {
38                      if (this.peopleNum() == this.maximum) { break; }
39                      // 有人上行且在该楼层出发,停下接人
40                      this.Open(i);           // 输出对应楼层开门信息
41                      this.goIn(i,p);         // 人走进电梯
42                      this.upQueue.kickOut(p); // 一旦进入电梯,则从队列中KickOut
43                      if (this.upQueue.isEmpty()
44                              || j >= this.upQueue.size() - 1) {
45                          if (j == 0 && !this.upQueue.isEmpty()) {
46                              p = this.upQueue.getQueueElm(0);
47                              continue;
48                          } else {
49                              break;
50                          }
51                      }
52                      p = this.upQueue.getQueueElm(j);
53                  }
54                  j++; // 继续看下一个人要不要进电梯
55              }
56              this.Close(i);
57          }
58      }

 

 

SS Multielevator Dependency Metrcis
ClassCyclicDcyDcy*DptDpt*
multielevator.Dispatch03422
multielevator.Elevator02223
multielevator.InputHandler02511
multielevator.MainClass03600
multielevator.Person00045
multielevator.TaskQueue01114

可以看出,即使到了第三次作业多部电梯,接相互依赖的类的数量也都是0,在类的安排和设计上还是比较妥当的。

 

分析自己程序的Bug

由于各种因素,前两次作业大家Hack兴致并不是很浓厚,没有被Hack。

Bug主要特征:

Time Limit Exceed 超时

在第二次作业中,我有一个强测点超时,是往队列里头插入导致的,后来全部改成在队尾插入就OK了。

在第三次作业中,有两个强测点超时,原因是我智障地在换乘人乘坐第一阶段电梯时,当场Sleep轮询看此人是否到达目的地,直接阻塞了输入,导致用时特别长。在Dispatch类中修复后,改成了同时把两阶段需求放入电梯。四十几条指令原本需要220秒的修复后80秒可跑完;原本需要163秒的60秒即可跑完。

还有一个小问题就是在同一时间同时输出了相同信息,大概是输出线程不安全的问题,把它锁起来以后解决。

bug位置与设计结构之间的相关性

 

多线程安全&各种坑

Wait() & NotifyAll

  • 永远在synchronize块中(对竞争资源进行加锁)和那个被多线程共享的对象上调用wait()

  • 永远在while循环中而不是if语句中调用wait()

  • 初学者尽量使用NotifyAll(),而不是Notify()

如果长时间没有输入的话,可以用wait()等待请求的到来,而不是sleep轮询

 
 1   public synchronized Person getMain() { // 获取主请求的函数
 2          Person p = new Person();
 3          if (!this.hasPeople()) { // 如果电梯没有人
 4              while (this.upQueue.isEmpty() && this.downQueue.isEmpty()) {
 5                  try { // 如果上行和下行的队列都是空的,那么等待
 6                      wait();
 7                  } catch (Exception e) {
 8                      System.out.println("ALL EMPTY ELEVATOR AND QUEUE");
 9                  }
10              } // 则从最早到队列中取出任务
11              if (!this.upQueue.isEmpty()) { //先往上走
12                  p = this.upQueue.getQueueElm(0);
13              } else if (!this.downQueue.isEmpty()) {
14                  p = this.downQueue.getQueueElm(0);
15              }
16          } else {
17              p = this.getPerson(0);
18          }
19          return p;
20      }
21     public synchronized void putInUp(Person person) {
22          this.upQueue.putQueueTail(person);
23          notifyAll(); // 在往电梯里放入请求的时候notifyall唤醒刚才wait() 的线程即可
24      } 
25  

 

Final修饰符

善用final修饰一旦赋值就不能再修改的变量,可以避免很多不必要的玄学bug,两种情况:

  1. 定义的时候直接赋值

  2. 定义的时候未赋值,在构造器中赋值

比如三个电梯不同的ID,最大荷载人数,不同的单层运行时间,开关门的时间,都可以用final来修饰

1  private final String id;
2  private final int maximum;
3  private final int runningTime;
4  private final int openCloseTime = 200;
5  public Elevator(String s, int max, int time) {
6          this.id = s;
7          this.maximum = max;
8          this.runningTime = time;
9      }

 

 

输出安全

这次的TimableOutput据说是线程不安全的(?)我出现过同一时间输出两条完全相同的语句,连时间戳也是相同的。为了保险起见,把每一个输出都用synchronize锁起来:

  

1  synchronized (TimableOutput.class) {
2              TimableOutput.println(
3                      String.format("OUT-%d-%d-%s",p.getId(),floor,this.id));
4          }

 

Hack

策略:盲测

在第二次测试中成功Hack他人5次。没有结合被测程序的代码设计结构来设计测试用例。

发现线程安全相关的问题:

 6022222-FROM--3-TO-15
 23333333-FROM--3-TO-14
 12345678-FROM--2-TO-13
 798-FROM-16-TO--1

该样例有同学输出一条信息后便卡住了,有一个人会在人还没进电梯之前就输出了OUT。

 

本单元的测试策略与第一单元测试策略的差异之处

本单元测试的可变性很多很复杂,正确解的形式五花八门,并且不像第一单元的表达式那样还有现成的工具可以化简判断正误。所以我只能小规模、10条一下的指令进行测试,没有进行压力测试。(我又不会像大佬那样写一个测评机出来TAT)

 

最后的感想

  • 多学习大佬们的博客,不需要从头造轮子,注意代码可扩展性

  • 看看《图解Java多线程设计模式》,不要从心理上惧怕多线程,战略上藐视它,战术上重视它

  • 先做好充足的思考可以减少编码时间,画个思维导图

  • 这次的电梯任务总算是面向对象了,把所有的类都看成一个实体,每个对象做好自己该做的事情

转载于:https://www.cnblogs.com/vanellopeblog/p/OOUnit2.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值