传说中的电梯居然就这样写完了~撒花?给今年的课程组笔芯,提供了输入输出接口包,不用像以前一样麻烦地处理输入,改善了面向过程和面向Wrong Format的程序设计。
程序设计思路和度量分析
三次作业都贯穿着一对函数:
Crawl & Slide
我的所有的程序一个中心,两个基本点,就是每次调度器会从队列里拿出一个“主请求”,如果这个主请求是向上走的,那么使用Crawl(int from, int to)函数,如果是向下走的,则使用Slide(int from , int to )函数;其中from
和to
是这个主请求的出发楼层和到达楼层。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))圈复杂度(独立路径的条数)。OCavg和WMC两个项目,分别代表类的方法的平均循环复杂度和总循环复杂度。
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
Dispatch.Dispatch(Elevator) | 1 | 1 | 1 |
Dispatch.NewRequest(PersonRequest) | 1 | 1 | 1 |
Dispatch.Work() | 1 | 2 | 2 |
Dispatch.run() | 1 | 2 | 2 |
Elevator.Elevator(int) | 1 | 1 | 1 |
Elevator.Run() | 1 | 1 | 1 |
Elevator.Stop() | 1 | 1 | 1 |
Elevator.isClosed() | 1 | 1 | 1 |
Elevator.isOpen() | 1 | 1 | 1 |
Elevator.isRunning() | 1 | 1 | 1 |
Elevator.onThisFloor(int) | 1 | 1 | 1 |
Elevator.onWhichFloor() | 1 | 1 | 1 |
MainClass.main(String[]) | 3 | 3 | 3 |
Person.Person(int,int,int) | 1 | 1 | 1 |
Person.getDstFloor() | 1 | 1 | 1 |
Person.getId() | 1 | 1 | 1 |
Person.getSrcFloor() | 1 | 1 | 1 |
Person.goIn() | 1 | 1 | 1 |
Person.goOut() | 1 | 1 | 1 |
Person.isInElevator() | 1 | 1 | 1 |
TaskQueue.getQueue() | 1 | 1 | 1 |
TaskQueue.isEmpty() | 1 | 1 | 1 |
TaskQueue.isFull() | 1 | 1 | 1 |
TaskQueue.pushELm() | 1 | 1 | 1 |
TaskQueue.putIntoQueue(PersonRequest) | 1 | 1 | 1 |
Class | OCavg | WMC |
---|---|---|
Dispatch | 1.25 | 5 |
Elevator | 1 | 8 |
MainClass | 3 | 3 |
Person | 1 | 7 |
TaskQueue | 1 | 5 |
傻瓜电梯的Dependency Metrcis
依赖度分析度量了类之间的依赖程度。有如下几种项目:
-
Cyclic:指和类直接或间接相互依赖的类的数量。这样的相互依赖可能导致代码难以理解和测试。
-
Dcy和Dcy*:计算了该类直接依赖的类的数量,带*表示包括了间接依赖的类。
-
Dpt和Dpt*:计算了直接依赖该类的类的数量,带*表示包括了间接依赖的类。
Class | Cyclic | Dcy | Dcy* | Dpt | Dpt* |
---|---|---|---|---|---|
Dispatch | 0 | 3 | 3 | 1 | 1 |
Elevator | 0 | 0 | 0 | 2 | 2 |
MainClass | 0 | 2 | 4 | 0 | 0 |
Person | 0 | 0 | 0 | 1 | 2 |
TaskQueue | 0 | 0 | 0 | 1 | 2 |
总的来说,傻瓜调度的电梯还是非常简单的,因为没有性能要求,我史无前例地(之后大概也不会)在强测中拿了100分。
捎带 - ALS
取出主请求,在上下运行的过程中如果有同一个方向的,然后到达了该楼层是电梯内某人的目的地,则下来,该楼层是upQueue或downQueue的其中一个人的始发地,则上车。
类的设计 - 可捎带电梯
-
MainClass - 主函数,处理输入
-
Elevator - 只是一个电梯厢,不会自己跑,全靠调度器安排
-
Person - 保存有ID,出发楼层,目标楼层,以及访问这些变量的方法和各种人的状态
-
Dispatch - 调度器,包含电梯厢,人,包含一个上行队列upQueue和一个下行队列downQueue,有最重要的crawl和slide方法,输出信息在Dispatch中完成
-
TaskQueue - 改成了以ArrayList为基础的队列,可在任意位置插入人,可以判断是否为空或满
类的设计与第一次作业基本保持一致,但是功能有一些变化,给调度器的职责更重了
(这个图画起来有一点专门为人服务的感觉哈哈哈啊)
ALS Complexity Metrcis
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
Dispatch.Dispatch(Elevator) | 1 | 1 | 1 |
Dispatch.NewRequest(PersonRequest) | 1 | 3 | 3 |
Dispatch.canEnd() | 2 | 4 | 5 |
Dispatch.carryLeftPeople(Elevator,Person) | 1 | 3 | 3 |
Dispatch.closeDoor(Elevator,int) | 1 | 2 | 2 |
Dispatch.crawl(int,int) | 11 | 20 | 21 |
Dispatch.getMain(Elevator,TaskQueue,TaskQueue) | 1 | 7 | 7 |
Dispatch.run() | 1 | 2 | 2 |
Dispatch.slide(int,int) | 11 | 20 | 21 |
Dispatch.work() | 6 | 14 | 14 |
Elevator.Close() | 1 | 1 | 1 |
Elevator.Elevator(int) | 1 | 1 | 1 |
Elevator.Open() | 1 | 1 | 1 |
Elevator.Run() | 1 | 1 | 1 |
Elevator.Stop() | 1 | 1 | 1 |
Elevator.getPerson(int) | 1 | 1 | 1 |
Elevator.goIn(Person) | 1 | 1 | 1 |
Elevator.goOut(Person) | 1 | 1 | 1 |
Elevator.hasPeople() | 1 | 1 | 1 |
Elevator.isClosed() | 1 | 1 | 1 |
Elevator.isGoDown() | 1 | 1 | 1 |
Elevator.isGoUp() | 1 | 1 | 1 |
Elevator.isOpen() | 1 | 1 | 1 |
Elevator.isRunning() | 1 | 1 | 1 |
Elevator.onThisFloor(int) | 1 | 1 | 1 |
Elevator.onWhichFloor() | 1 | 1 | 1 |
Elevator.peopleNum() | 1 | 1 | 1 |
Elevator.setGoDown() | 1 | 1 | 1 |
Elevator.setGoUp() | 1 | 1 | 1 |
MainClass.main(String[]) | 3 | 5 | 5 |
Person.Person() | 1 | 1 | 1 |
Person.Person(int,int,int) | 1 | 1 | 3 |
Person.getDirection() | 1 | 1 | 1 |
Person.getDstFloor() | 1 | 1 | 1 |
Person.getId() | 1 | 1 | 1 |
Person.getSrcFloor() | 1 | 1 | 1 |
Person.goIn() | 1 | 1 | 1 |
Person.goOut() | 1 | 1 | 1 |
Person.isGoingDown() | 1 | 1 | 1 |
Person.isGoingUp() | 1 | 1 | 1 |
Person.isInElevator() | 1 | 1 | 1 |
TaskQueue.getQueueElm(int) | 1 | 1 | 1 |
TaskQueue.insertQueue(Person,int) | 9 | 9 | 9 |
TaskQueue.isEmpty() | 1 | 1 | 1 |
TaskQueue.isFull() | 1 | 1 | 1 |
TaskQueue.kickOut(Person) | 1 | 1 | 1 |
TaskQueue.pushELm() | 1 | 1 | 1 |
TaskQueue.putQueueHead(Person) | 1 | 1 | 1 |
TaskQueue.putQueueTail(Person) | 1 | 1 | 1 |
TaskQueue.size() | 1 | 1 | 1 |
Class | OCavg | WMC |
---|---|---|
Dispatch | 5.5 | 55 |
Elevator | 1 | 19 |
MainClass | 5 | 5 |
Person | 1.18 | 13 |
TaskQueue | 1.89 | 17 |
ALS Dependency Metrcis
Class | Cyclic | Dcy | Dcy* | Dpt | Dpt* |
---|---|---|---|---|---|
Dispatch | 0 | 3 | 3 | 1 | 1 |
Elevator | 0 | 1 | 1 | 2 | 2 |
MainClass | 0 | 2 | 4 | 0 | 0 |
Person | 0 | 0 | 0 | 3 | 4 |
TaskQueue | 0 | 1 | 1 | 1 | 2 |
多电梯 - SS
我的设计思路有以下几点:
-
先判断是否能直达,若能直达,Dispatch把任务放到对应电梯的队列里,优先分配A电梯,其次B、C。 若不能直达,则给出换乘方案:把两段任务都先直接放到放到相应的电梯队列里,但是第二个任务的Validity为False,即便电梯到了他的出发楼层,他也不能“进入电梯”。
-
通过计算,到距离这个人最近的换乘点进行换乘,到达换乘点后,若发现这个人是一个换乘的前半段任务,那么把第二个人的Validity设置为True,他就可以在Crawl和Slide中上电梯了。
-
每个电梯只能从自己的队列里取任务,不能关心外面的Dispatch和InputHandler,上下过程中可捎带。
-
所有输出信息都写在Elevator当中,剩下可以安心运行而不关心琐碎的事情,在for循环中Arrive,Crawl是向上运行,Slide是向下运行。进进出出,开门关门都是电梯的事情。
-
换乘人在第一阶段运行中,输入依然可以正确进行,只要不输入
Command + D
。 -
如果主请求运输完毕后,电梯会先将被捎带还在电梯内的乘客送到目的地,然后再取新的请求。
类的设计 - 多电梯
-
MainClass - 主函数,启动一个输入线程和一个调度器线程
-
Elevator - 一个电梯厢,包含一个上行队列upQueue和一个下行队列downQueue,本身是一个Thread,包含所有的输出信息;调度器只负责往电梯的队列里塞人,电梯需要从自己的队列里取用需求,并且上下运动捎带,有最重要的crawl和slide方法
-
Person - 保存有ID,出发楼层,目标楼层,以及访问这些变量的方法和各种人的状态;
增加了Ancestor和exchange变量,如果exchange为true,那么表明有换乘的“前人”,通过检测Ancestor的楼层状态可以判断这个人是否能够进入电梯
-
Dispatch - 调度器,包含电梯厢,人,主队列,启用三个电梯线程,决定某人请求是否需要换乘,且应该放到哪个电梯的哪个队列里。
-
TaskQueue - 以ArrayList为基础的队列,可在任意位置插入人,可以判断是否为空或满
-
InputHandler - 单独开一个线程来处理输入,并将需求传递给Dispatch
SS Multielevator Complexity Metrcis
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
multielevator.Dispatch.AllStop() | 2 | 3 | 4 |
multielevator.Dispatch.Dispatch(Elevator,Elevator,Elevator) | 1 | 1 | 1 |
multielevator.Dispatch.canStop() | 1 | 1 | 1 |
multielevator.Dispatch.nearExchangeab(int) | 5 | 1 | 7 |
multielevator.Dispatch.nearExchangeac(int) | 2 | 1 | 2 |
multielevator.Dispatch.nearExchangebc(int) | 8 | 1 | 10 |
multielevator.Dispatch.putExchangePerson(Person,int,int,int,Elevator,Elevator) | 1 | 3 | 3 |
multielevator.Dispatch.putInPerson(Person) | 1 | 14 | 14 |
multielevator.Dispatch.putStraight(Person,int,int) | 4 | 7 | 7 |
multielevator.Dispatch.reachA(int) | 2 | 1 | 3 |
multielevator.Dispatch.reachB(int) | 2 | 1 | 5 |
multielevator.Dispatch.reachC(int) | 2 | 1 | 2 |
multielevator.Dispatch.run() | 1 | 1 | 1 |
multielevator.Dispatch.straight(int,int) | 4 | 6 | 7 |
multielevator.Dispatch.work() | 1 | 1 | 1 |
multielevator.Elevator.Arrive(int) | 1 | 1 | 1 |
multielevator.Elevator.Close(int) | 1 | 2 | 2 |
multielevator.Elevator.Crawl(int,int) | 12 | 20 | 22 |
multielevator.Elevator.Elevator(String,int,int) | 1 | 1 | 1 |
multielevator.Elevator.Open(int) | 1 | 2 | 2 |
multielevator.Elevator.PickUp(int,int) | 1 | 4 | 4 |
multielevator.Elevator.Run() | 1 | 1 | 1 |
multielevator.Elevator.Slide(int,int) | 12 | 20 | 22 |
multielevator.Elevator.Stop() | 1 | 1 | 1 |
multielevator.Elevator.allEmpty() | 2 | 5 | 6 |
multielevator.Elevator.carryLeftPeople() | 1 | 3 | 3 |
multielevator.Elevator.getMain() | 1 | 7 | 7 |
multielevator.Elevator.getPerson(int) | 1 | 1 | 1 |
multielevator.Elevator.goIn(int,Person) | 2 | 2 | 2 |
multielevator.Elevator.goOut(int,Person) | 1 | 1 | 1 |
multielevator.Elevator.hasPeople() | 1 | 1 | 1 |
multielevator.Elevator.isClosed() | 1 | 1 | 1 |
multielevator.Elevator.isGoDown() | 1 | 1 | 1 |
multielevator.Elevator.isGoUp() | 1 | 1 | 1 |
multielevator.Elevator.isOpen() | 1 | 1 | 1 |
multielevator.Elevator.isRunning() | 1 | 1 | 1 |
multielevator.Elevator.onThisFloor(int) | 1 | 1 | 1 |
multielevator.Elevator.onWhichFloor() | 1 | 1 | 1 |
multielevator.Elevator.peopleNum() | 1 | 1 | 1 |
multielevator.Elevator.putInDown(Person) | 1 | 1 | 1 |
multielevator.Elevator.putInUp(Person) | 1 | 1 | 1 |
multielevator.Elevator.run() | 1 | 17 | 17 |
multielevator.Elevator.setGoDown() | 1 | 1 | 1 |
multielevator.Elevator.setGoUp() | 1 | 1 | 1 |
multielevator.Elevator.setStopSignal() | 1 | 1 | 1 |
multielevator.InputHandler.InputHandler(Dispatch) | 1 | 1 | 1 |
multielevator.InputHandler.run() | 3 | 8 | 8 |
multielevator.MainClass.main(String[]) | 1 | 1 | 1 |
multielevator.Person.ArriveDst() | 1 | 1 | 1 |
multielevator.Person.Person() | 1 | 1 | 1 |
multielevator.Person.Person(int,int,int) | 1 | 1 | 3 |
multielevator.Person.getDirection() | 1 | 1 | 1 |
multielevator.Person.getDstFloor() | 1 | 1 | 1 |
multielevator.Person.getId() | 1 | 1 | 1 |
multielevator.Person.getSrcFloor() | 1 | 1 | 1 |
multielevator.Person.goIn() | 1 | 1 | 1 |
multielevator.Person.goOut() | 1 | 1 | 1 |
multielevator.Person.hasArrivedFirst() | 3 | 2 | 3 |
multielevator.Person.isExchange() | 1 | 1 | 1 |
multielevator.Person.isGoingDown() | 1 | 1 | 1 |
multielevator.Person.isGoingUp() | 1 | 1 | 1 |
multielevator.Person.isInElevator() | 1 | 1 | 1 |
multielevator.Person.isOnThisFloor(int) | 1 | 1 | 1 |
multielevator.Person.needExchange(Person) | 1 | 1 | 1 |
multielevator.TaskQueue.getQueueElm(int) | 1 | 1 | 1 |
multielevator.TaskQueue.isEmpty() | 1 | 1 | 1 |
multielevator.TaskQueue.isFull() | 1 | 1 | 1 |
multielevator.TaskQueue.kickOut(Person) | 1 | 1 | 1 |
multielevator.TaskQueue.putQueueHead(Person) | 1 | 1 | 1 |
multielevator.TaskQueue.putQueueTail(Person) | 1 | 1 | 1 |
multielevator.TaskQueue.size() | 1 | 1 | 1 |
Class | OCavg | WMC |
---|---|---|
multielevator.Dispatch | 3.53 | 53 |
multielevator.Elevator | 2.47 | 74 |
multielevator.InputHandler | 3 | 6 |
multielevator.MainClass | 1 | 1 |
multielevator.Person | 1.25 | 20 |
multielevator.TaskQueue | 1 | 7 |
我把所有源文件放在了一个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
Class | Cyclic | Dcy | Dcy* | Dpt | Dpt* |
---|---|---|---|---|---|
multielevator.Dispatch | 0 | 3 | 4 | 2 | 2 |
multielevator.Elevator | 0 | 2 | 2 | 2 | 3 |
multielevator.InputHandler | 0 | 2 | 5 | 1 | 1 |
multielevator.MainClass | 0 | 3 | 6 | 0 | 0 |
multielevator.Person | 0 | 0 | 0 | 4 | 5 |
multielevator.TaskQueue | 0 | 1 | 1 | 1 | 4 |
可以看出,即使到了第三次作业多部电梯,接相互依赖的类的数量也都是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,两种情况:
-
定义的时候直接赋值
-
定义的时候未赋值,在构造器中赋值
比如三个电梯不同的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多线程设计模式》,不要从心理上惧怕多线程,战略上藐视它,战术上重视它
-
先做好充足的思考可以减少编码时间,画个思维导图
-
这次的电梯任务总算是面向对象