一、代码分析
第五次作业
设计策略
第五次作业是OO课程电梯系列作业的开篇之作,也是多线程实践的开始。本次作要求模拟一部不限制人数的电梯的运行,并要求电梯的调度效果比ALS捎带策略要好。
经过多项式求导的三次作业试炼,在开始编写本次作业的代码之前,我对本次作业的代码结构进行了一个比较详细的规划,一来能够明晰代码的结构、提高编码和阅读的体验,二来能够提高可扩展性,为后两次作业的迭代减少作业量。
程序结构
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
com.homework5.Main.main(String[]) | 1 | 1 | 1 |
com.homework5.data.Elevator.Elevator() | 1 | 1 | 1 |
com.homework5.data.Elevator.closeDoor() | 1 | 2 | 2 |
com.homework5.data.Elevator.getCurrentFloor() | 1 | 1 | 1 |
com.homework5.data.Elevator.getPassengerCount() | 1 | 1 | 1 |
com.homework5.data.Elevator.getScanDirection() | 1 | 1 | 1 |
com.homework5.data.Elevator.isAtBottom() | 1 | 1 | 1 |
com.homework5.data.Elevator.isAtTop() | 1 | 1 | 1 |
com.homework5.data.Elevator.isEmpty() | 1 | 1 | 1 |
com.homework5.data.Elevator.openDoor() | 1 | 2 | 2 |
com.homework5.data.Elevator.passengerArrived() | 1 | 3 | 3 |
com.homework5.data.Elevator.passengerGetIn(ArrayList<Person>) | 2 | 2 | 3 |
com.homework5.data.Elevator.passengerGetOut() | 1 | 3 | 3 |
com.homework5.data.Elevator.runDown() | 1 | 2 | 2 |
com.homework5.data.Elevator.runUP() | 1 | 2 | 2 |
com.homework5.data.Elevator.setScanDirection(ElevatorState) | 1 | 1 | 1 |
com.homework5.data.Elevator.toString() | 1 | 1 | 1 |
com.homework5.data.Person.Person() | 1 | 1 | 1 |
com.homework5.data.Person.Person(int,int,int) | 1 | 1 | 2 |
com.homework5.data.Person.getCurrentFloor() | 1 | 1 | 1 |
com.homework5.data.Person.getId() | 1 | 1 | 1 |
com.homework5.data.Person.getPersonState() | 1 | 1 | 1 |
com.homework5.data.Person.getTargetDirection() | 1 | 1 | 1 |
com.homework5.data.Person.getTargetFloor() | 1 | 1 | 1 |
com.homework5.data.Person.toString() | 1 | 1 | 1 |
com.homework5.util.Inputer.run() | 3 | 4 | 4 |
com.homework5.util.Scheduler.add(Person) | 1 | 1 | 1 |
com.homework5.util.Scheduler.finishInput() | 1 | 1 | 1 |
com.homework5.util.Scheduler.getNextRunDirection() | 17 | 13 | 17 |
com.homework5.util.Scheduler.isEmpty() | 1 | 1 | 1 |
com.homework5.util.Scheduler.passengerAtCurrentFloor() | 1 | 4 | 4 |
com.homework5.util.Scheduler.remove(ArrayList<Person>) | 1 | 2 | 2 |
com.homework5.util.Scheduler.remove(Person) | 1 | 1 | 1 |
com.homework5.util.Scheduler.remove(int) | 1 | 1 | 1 |
com.homework5.util.Scheduler.run() | 5 | 17 | 18 |
com.homework5.util.Scheduler.set(int,Person) | 1 | 1 | 1 |
通过Metrics工具进行分析,本次作业的代码有两个方法比较复杂。分别是获取电梯下一次运行方向的方法和电梯调度程序的run方法。
第六次作业
设计策略
第六次作业相较第五次作业增加了电梯数量、电梯限载以及楼层非均匀分布,在程序的结构方面,我的代码不需要有大的调整,只需要在第五次作业基础上进行扩充即可。
对电梯限载的处理,只需要调整电梯调度器严格控制进入电梯的人数即可。对楼层的不均匀化,使用一个静态数组存储楼层数据,用一个指针变量来获取当前电梯所处的楼层。
第六次作业最难的点在于如何向多台电梯分配需求。我采用的策略是在电梯调度线程和输入线程之间增加一层调度器作为总调度器,向各个电梯分配需求。分配策略则是直接均匀分配,为每个电梯分配需求。事实上,在需求较多时,这种分配策略反而具有较好的效果。从总体上来看,总调度器维护一个大队列,并在队列不为空时为每个电梯调度器分配一个等待队列,电梯调度器获得队列之后操作电梯运输乘客,这样的结构使得电梯调度器的等待队列大小可以大于电梯的容量,而电梯在运行过程中自行控制人数,减小总调度的压力。
程序结构
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
com.homework6.Main.main(String[]) | 1 | 1 | 1 |
com.homework6.data.Elevator.Elevator() | 1 | 1 | 1 |
com.homework6.data.Elevator.Elevator(String) | 1 | 1 | 1 |
com.homework6.data.Elevator.closeDoor() | 1 | 2 | 2 |
com.homework6.data.Elevator.getCapacity() | 1 | 1 | 1 |
com.homework6.data.Elevator.getCurrentFloor() | 1 | 1 | 1 |
com.homework6.data.Elevator.getName() | 1 | 1 | 1 |
com.homework6.data.Elevator.getPassengerCount() | 1 | 1 | 1 |
com.homework6.data.Elevator.getScanDirection() | 1 | 1 | 1 |
com.homework6.data.Elevator.isAtBottom() | 1 | 1 | 1 |
com.homework6.data.Elevator.isAtTop() | 1 | 1 | 1 |
com.homework6.data.Elevator.isEmpty() | 1 | 1 | 1 |
com.homework6.data.Elevator.openDoor() | 1 | 2 | 2 |
com.homework6.data.Elevator.passengerArrived() | 1 | 3 | 3 |
com.homework6.data.Elevator.passengerGetIn(ArrayList<Person>) | 2 | 2 | 3 |
com.homework6.data.Elevator.passengerGetOut() | 1 | 3 | 3 |
com.homework6.data.Elevator.runDown() | 1 | 2 | 2 |
com.homework6.data.Elevator.runUP() | 1 | 2 | 2 |
com.homework6.data.Elevator.setScanDirection(ElevatorState) | 1 | 1 | 1 |
com.homework6.data.Elevator.toString() | 1 | 1 | 1 |
com.homework6.data.Person.Person() | 1 | 1 | 1 |
com.homework6.data.Person.Person(int,int,int) | 1 | 1 | 2 |
com.homework6.data.Person.getCurrentFloor() | 1 | 1 | 1 |
com.homework6.data.Person.getId() | 1 | 1 | 1 |
com.homework6.data.Person.getPersonState() | 1 | 1 | 1 |
com.homework6.data.Person.getTargetDirection() | 1 | 1 | 1 |
com.homework6.data.Person.getTargetFloor() | 1 | 1 | 1 |
com.homework6.data.Person.toString() | 1 | 1 | 1 |
com.homework6.util.ElevatorScheduler.ElevatorScheduler(String) | 1 | 1 | 1 |
com.homework6.util.ElevatorScheduler.add(Person) | 1 | 1 | 1 |
com.homework6.util.ElevatorScheduler.finishInput() | 1 | 1 | 1 |
com.homework6.util.ElevatorScheduler.getName() | 1 | 1 | 1 |
com.homework6.util.ElevatorScheduler.getNextRunDirection() | 17 | 13 | 17 |
com.homework6.util.ElevatorScheduler.getRestCapacity() | 1 | 1 | 1 |
com.homework6.util.ElevatorScheduler.isBusy() | 1 | 1 | 1 |
com.homework6.util.ElevatorScheduler.isEmpty() | 1 | 1 | 1 |
com.homework6.util.ElevatorScheduler.passengerAtCurrentFloor() | 3 | 4 | 5 |
com.homework6.util.ElevatorScheduler.remove(ArrayList<Person>) | 1 | 2 | 2 |
com.homework6.util.ElevatorScheduler.remove(Person) | 1 | 1 | 1 |
com.homework6.util.ElevatorScheduler.remove(int) | 1 | 1 | 1 |
com.homework6.util.ElevatorScheduler.run() | 5 | 17 | 18 |
com.homework6.util.ElevatorScheduler.set(int,Person) | 1 | 1 | 1 |
com.homework6.util.ElevatorScheduler.setBusy(boolean) | 1 | 1 | 1 |
com.homework6.util.Inputer.run() | 3 | 4 | 4 |
com.homework6.util.RequestScheduler.ElevatorStart() | 1 | 2 | 2 |
com.homework6.util.RequestScheduler.add(Person) | 1 | 1 | 1 |
com.homework6.util.RequestScheduler.finishInput() | 1 | 1 | 1 |
com.homework6.util.RequestScheduler.isEmpty() | 1 | 1 | 1 |
com.homework6.util.RequestScheduler.remove(ArrayList<Person>) | 1 | 2 | 2 |
com.homework6.util.RequestScheduler.remove(Person) | 1 | 1 | 1 |
com.homework6.util.RequestScheduler.remove(int) | 1 | 1 | 1 |
com.homework6.util.RequestScheduler.run() | 6 | 10 | 11 |
com.homework6.util.RequestScheduler.set(int,Person) | 1 | 1 | 1 |
com.homework6.util.RequestScheduler.setElevatorNum(int) | 1 | 1 | 1 |
通过Metrics工具进行分析,本次作业的代码有三个方法比较复杂。分别是获取电梯下一次运行方向的方法和两个调度程序的run方法。
第七次作业
设计策略
第七次作业是电梯系列的最后一次作业,也是最难的一次。较第六次作业,这次作业增加了电梯动态加入的功能,并且规定了不同种电梯的可停靠楼层和运行速度。这就导致了某些乘客必须换乘才能够到达目的地,并且电梯的运行速度不同导致了在选择不同的电梯会使得性能有所不同。为了实现这次作业的需求,需要将人的需求进行拆分和细化,并且增加电梯调度和总调度的通信路径。对需求的拆分,我采用静态中转的策略,在输入线程解析到一个需求时,就规划好这个人的换乘或中转策略。在选择电梯时,采用随机的策略随机分配到可乘坐的电梯。
程序结构
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
com.homework77.Main.main(String[]) | 1 | 1 | 1 |
com.homework77.data.Elevator.Elevator() | 1 | 1 | 1 |
com.homework77.data.Elevator.Elevator(ElevatorType,String,int,int[],int[],int,int,int,int) | 1 | 1 | 1 |
com.homework77.data.Elevator.closeDoor() | 1 | 2 | 2 |
com.homework77.data.Elevator.getCapacity() | 1 | 1 | 1 |
com.homework77.data.Elevator.getCurrentFloor() | 1 | 1 | 1 |
com.homework77.data.Elevator.getName() | 1 | 1 | 1 |
com.homework77.data.Elevator.getPassengerCount() | 1 | 1 | 1 |
com.homework77.data.Elevator.getScanDirection() | 1 | 1 | 1 |
com.homework77.data.Elevator.isAtBottom() | 1 | 1 | 1 |
com.homework77.data.Elevator.isAtTop() | 1 | 1 | 1 |
com.homework77.data.Elevator.isEmpty() | 1 | 1 | 1 |
com.homework77.data.Elevator.openDoor() | 1 | 2 | 2 |
com.homework77.data.Elevator.passengerArrived() | 1 | 3 | 3 |
com.homework77.data.Elevator.passengerGetIn(ArrayList<PersonRequest1>) | 2 | 2 | 3 |
com.homework77.data.Elevator.passengerGetOut() | 1 | 3 | 3 |
com.homework77.data.Elevator.runDown() | 1 | 2 | 2 |
com.homework77.data.Elevator.runUP() | 1 | 2 | 2 |
com.homework77.data.Elevator.setElevatorName(String) | 1 | 1 | 1 |
com.homework77.data.Elevator.setScanDirection(ElevatorState) | 1 | 1 | 1 |
com.homework77.data.Elevator.toString() | 1 | 1 | 1 |
com.homework77.data.Person.Person() | 1 | 1 | 1 |
com.homework77.data.Person.Person(int,PersonRequest1) | 1 | 1 | 1 |
com.homework77.data.Person.Person(int,int,int) | 1 | 1 | 2 |
com.homework77.data.Person.elevatorCanTake() | 1 | 1 | 1 |
com.homework77.data.Person.getCurrentFloor() | 1 | 1 | 1 |
com.homework77.data.Person.getCurrentRequest() | 1 | 1 | 1 |
com.homework77.data.Person.getId() | 1 | 1 | 1 |
com.homework77.data.Person.getInElevator() | 1 | 1 | 1 |
com.homework77.data.Person.getPersonState() | 1 | 1 | 1 |
com.homework77.data.Person.getTargetDirection() | 1 | 1 | 1 |
com.homework77.data.Person.getTargetFloor() | 1 | 1 | 1 |
com.homework77.data.Person.isComfortable() | 1 | 1 | 1 |
com.homework77.data.Person.isNaive() | 1 | 1 | 1 |
com.homework77.data.Person.needTransfer() | 1 | 1 | 1 |
com.homework77.data.Person.setPersonState(PersonState) | 1 | 1 | 1 |
com.homework77.data.Person.toString() | 1 | 1 | 1 |
com.homework77.data.Person.update() | 1 | 1 | 1 |
com.homework77.data.PersonRequest1.PersonRequest1() | 1 | 1 | 1 |
com.homework77.data.PersonRequest1.PersonRequest1(int,int,int,boolean,ArrayList<ElevatorType>) | 1 | 1 | 2 |
com.homework77.data.PersonRequest1.getCurrentFloor() | 1 | 1 | 1 |
com.homework77.data.PersonRequest1.getElevatorType() | 1 | 1 | 1 |
com.homework77.data.PersonRequest1.getId() | 1 | 1 | 1 |
com.homework77.data.PersonRequest1.getTargetDirection() | 1 | 1 | 1 |
com.homework77.data.PersonRequest1.getTargetFloor() | 1 | 1 | 1 |
com.homework77.data.PersonRequest1.needTransfer() | 1 | 1 | 1 |
com.homework77.data.PersonRequest1.toString() | 1 | 1 | 1 |
com.homework77.util.ElevatorFactory.canDirectArrive(int,int) | 1 | 7 | 7 |
com.homework77.util.ElevatorFactory.getElevator(String,String) | 5 | 2 | 5 |
com.homework77.util.ElevatorFactory.isContainKey(int[],int) | 4 | 1 | 5 |
com.homework77.util.ElevatorFactory.parseRequest(int,int,int) | 2 | 3 | 6 |
com.homework77.util.ElevatorScheduler.ElevatorScheduler(String,String) | 1 | 1 | 1 |
com.homework77.util.ElevatorScheduler.add(PersonRequest1) | 1 | 1 | 1 |
com.homework77.util.ElevatorScheduler.aoLiGei() | 1 | 1 | 1 |
com.homework77.util.ElevatorScheduler.finishInput() | 1 | 1 | 1 |
com.homework77.util.ElevatorScheduler.getElevatorName() | 1 | 1 | 1 |
com.homework77.util.ElevatorScheduler.getNextRunDirection() | 17 | 13 | 17 |
com.homework77.util.ElevatorScheduler.getRestCapacity() | 1 | 1 | 1 |
com.homework77.util.ElevatorScheduler.isEmpty() | 1 | 1 | 1 |
com.homework77.util.ElevatorScheduler.passengerAtCurrentFloor() | 3 | 4 | 5 |
com.homework77.util.ElevatorScheduler.passengerAtCurrentFloor(int) | 3 | 3 | 4 |
com.homework77.util.ElevatorScheduler.remove(ArrayList<PersonRequest1>) | 1 | 2 | 2 |
com.homework77.util.ElevatorScheduler.remove(PersonRequest1) | 1 | 1 | 1 |
com.homework77.util.ElevatorScheduler.remove(int) | 1 | 1 | 1 |
com.homework77.util.ElevatorScheduler.run() | 6 | 17 | 19 |
com.homework77.util.ElevatorScheduler.set(int,PersonRequest1) | 1 | 1 | 1 |
com.homework77.util.ElevatorScheduler.setElevatorName(String) | 1 | 1 | 1 |
com.homework77.util.Inputer.Inputer() | 1 | 1 | 1 |
com.homework77.util.Inputer.run() | 3 | 6 | 6 |
com.homework77.util.ReLock.awaitA() | 1 | 2 | 2 |
com.homework77.util.ReLock.lock() | 1 | 1 | 1 |
com.homework77.util.ReLock.singnalA() | 1 | 1 | 1 |
com.homework77.util.ReLock.unlock() | 1 | 1 | 1 |
com.homework77.util.RequestScheduler.ElevatorStart() | 1 | 1 | 1 |
com.homework77.util.RequestScheduler.RequestScheduler() | 1 | 1 | 1 |
com.homework77.util.RequestScheduler.add(Person) | 1 | 1 | 1 |
com.homework77.util.RequestScheduler.addElevator(String,String) | 2 | 2 | 5 |
com.homework77.util.RequestScheduler.aoLiGei() | 1 | 1 | 1 |
com.homework77.util.RequestScheduler.finishInput() | 1 | 1 | 1 |
com.homework77.util.RequestScheduler.isEmpty() | 1 | 1 | 1 |
com.homework77.util.RequestScheduler.isWait() | 4 | 2 | 4 |
com.homework77.util.RequestScheduler.remove(ArrayList<Person>) | 1 | 2 | 2 |
com.homework77.util.RequestScheduler.remove(Person) | 1 | 1 | 1 |
com.homework77.util.RequestScheduler.remove(int) | 1 | 1 | 1 |
com.homework77.util.RequestScheduler.run() | 6 | 15 | 17 |
com.homework77.util.RequestScheduler.update(ArrayList<PersonRequest1>) | 1 | 3 | 3 |
com.homework77.util.RequestScheduler.update(PersonRequest1) | 1 | 3 | 3 |
通过Metrics工具进行分析,本次作业的代码有三个方法比较复杂。分别是获取电梯下一次运行方向的方法和两个调度程序的run方法。
二、多线程学习感想
三次作业中,对电梯的控制、需求的调度、需求的获取,三者并立,正是多线程工具发挥作用的好场所。初次学习并使用多线程,一是感叹于多线程强大的工具性能够解决很多在之前一MAIN到底的程序中无法解决的问题,二是感觉到多线程虽然好用,但是没有对程序结构的清楚把握,没有对多线程工具的透彻理解,多线程带来的一系列BUG实在是很难解决。
通过这几次作业的学习,我对多线程有了一个比较具体的认识,我认为在使用多线程工具之前,一定要清晰的掌握自己程序的框架和实现方法,将自己的程序抽象成几个独立数据流,来对应将要生成的线程。除此之外,对各个线程之间的数据交互、协同合作要有把握。这样在使用多线程工具时,才能得心应手,不出错误。
就从最后一次作业进行分析,这次作业有三类线程,输入线程、总调度线程、电梯调度线程,而电梯调度线程会有并行的多个。各个线程之间的功能交互如下图。通过明确程序中可能产生的数据冲突,对合适的类或对象进行加锁,来保证程序运行中的线程安全。与此同时为了减少CPU空转的情形,需要明确各类线程何时进入阻塞、何时结束的条件,节省资源,避免轮询情况。就拿这次作业来说,对输入线程,当输入流未结束时,输入线程就会被阻塞,一旦有合法的需求产生,输入线程就会通知总调度线程,并想总调度线程的需求队列中增加需求,一旦输入线程结束,则通知总调度线程。对总调度线程,当需求队列不为空时,就进行需求向各个电梯的分配,否则进入阻塞状态,一旦输入线程通知结束且需求队列为空,就结束总调度线程,并通知各个电梯调度线程。对各个电梯调度线程,对接收到的需求进行调度,完成乘客需求,并通知总调度线程,若等待队列为空,电梯停运,待有需求时开始运转。到总调度线程结束且等待队列为空,电梯所有需求完毕时,该电梯调度线程结束。在清楚这些线程之间的协作关系之后,最后的代码编写也就能够得心应手了。
三、程序的可扩展性分析
良好的代码架构能够使得作者在后续的迭代中减少工作量,在这三次作业中也是如此,单电梯向多电梯的迭代,多电梯向动态增加电梯、增加换乘需求的迭代,良好的扩展性能够把每次作业需要花费的时间减到最小。
对电梯这一系列的作业来说,我对自己的代码架构比较满意。下面从OO设计中的5个经典原则进行分析。
SRP原则:每个类方法都只有一个明确的职责。我在代码的编写时,将电梯模块与调度模块分离,将需求模块与乘客模块分离,在类的职责方面,每个类的职责相对独立。但是在一些类的方法中,我对一些功能的抽象做的还不够好,导致方法的复杂度高。
OCP原则:无需修改已有实现(close),而是通过扩展来增加新功能(open)。在进行后两次作业的迭代时,我对之前的代码几乎没有修改,通过增加总调度器,实例化多个电梯调度器来实现功能(第六次),增加电梯工厂类,扩展需求类来实现功能(第七次)。
LSP原则:任何父类出现的地方都可以使用子类来代替,并不会导致使用相应类的程序出现错误。在这次作业中,我通过继承Thread类来实现多种线程不同功能。
ISP原则和DIP原则在本次作业中未涉及,不做分析。
四、bug分析
第五次作业
强测部分:顺利通过
互测部分:电梯调度程序在需求队列为空时没有正常阻塞,导致轮询使得CPU时间超时。
第六次作业
强测部分和互测部分都出现了一个bug,原因是计算电梯可装载人数的公式出错。
第七次作业
强测和互测部分都没有错误。
hack策略
事实上,在这一系列的作业中,同屋里同学几乎都没有bug,一来是同学们编码水平的提升,二来是我的hack策略较为原始,没有构建相应的评测机来实现自动评测,所以hack效果不甚理想。
五、心得体会
这一单元的多线程电梯作业从难度上来讲并不是很难,从题目设计到实现,我认为老师和助教组更大的目的是让我们掌握多线程这一工具,了解它的原理和使用方法。
我是第一次接触多线程程序设计,在面对第一次作业时,我感受到的是无从下手的慌乱,多亏了助教组的答疑以及讨论区同学的分享,我在完成代码时的问题都顺利得到了解决。一个单元的学习,让我对多线程这一工具有了自己的认识,也能够顺利地利用多线程的相关工具解决部分问题。不得不说,多线程是个很神奇的工具,在刚开始听课时,我对JVM的运行机制和时间片的分配几乎是一头雾水,但是通过自己编码实践、完成作业,我对多线程才产生了较为清楚额度认识。其次,多线程的安全问题十分重要,线程安全容器和线程安全类的使用能够有效的解决线程安全可能出现的问题。