面向对象第二单元总结
面向对象第二单元总结
一、第二单元的任务
在第二单元,我们需要模拟电梯对实时输入的运送请求的调度。
本单元对电梯的调度模拟分为三个不同的阶段:
1、第一阶段,我们仅有一部电梯。对于按时到达的运送请求,我们采取的是FAFS(先来先服务)的调度策略。每次运送过程电梯仅仅搭载一位乘客。
2、第二阶段,我们同样只有一部电梯,但是在调度策略上做出了改变。这次,我们采取捎带策略,即在电梯上行(下行)的过程中,会捎带上所有需要上行(下行)的运送请求。并且,电梯所停靠的楼层和携带人员没有限制。
3、第三阶段,这一次,我们拥有了三部停靠楼层,运输速度和最大载客量均不同的三部电梯,在调度策略上随兴发挥。由于电梯的停靠楼层各不相同,载客量有上限。我们必须考虑换乘,电梯选择等问题。
本单元,我们必须采用多线程编程来满足实时输入、输出的电梯运行模拟。这是我第一次接触多线程编程,逐步完成整个单元对我来说算得上一次不小的挑战。
由于输入、输出接口助教都以jar包的形式发放,我们可以将重点放在电梯架构的设计上了。
二、设计模式
(1)第一阶段电梯
在第一阶段的设计过程中,我第一反应是"生产者-消费者模型"。我一共设计了两个线程(不含主线程)。输入线程作为生产者,将所有输入请求放入请求队列(共享对象,托盘),电梯作为消费者,在请求队列中按序拿出需求。
在这一阶段,我将调度器和请求队列(共享对象)看作一体,在调度器中,所有对请求队列进行的操作,如add,isEmpty,remove等方法,均采用synchronized修饰,保证输入线程(生产者)和电梯(消费者)对请求队列(共享对象)操作的同步性。
关于进程的结束条件:输入线程在键入ctrl+D之后结束,电梯进程在输入线程结束且请求队列为空的条件下才结束。在电梯进程运行时,执行轮询策略,如果请求队列为空,sleep一段时间(1000ms),否则取出请求队列队首进行执行。
(2)第二阶段电梯
与第一阶段电梯相比,第二阶段阶段电梯我做出了较大重构,并抛弃了轮询策略,采用wait和notify管理线程。
首先,我将请求队列与调度器分离开,除主请求外,现在有输入线程、调度器线程、电梯线程,一共三个线程。
输入线程和调度器之间共享请求队列。请求队列单独设立一个类,类中所有对请求队列进行操作和查询的方法均用synchronized修饰。调度器线程在判断请求对列为空时,进入wait状态;输入线程得到一个新请求时notify唤醒调度器。(这两个线程共享一把锁lock用于wait和notify)
调度器和这一部电梯共享一个请求队列,这个请求队列由电梯管理维护,代表由调度器分配给该电梯的请求。电梯在判断自己的请求队列和正搭载的人为空时进入wait状态,调度器在分配给这部电梯任务时,即将主请求队列中的请求分配给该部电梯的请求队列时,notify唤醒被分配任务且处于wait状态的电梯。由于这阶段仅有一部电梯,主请求队列中的请求全部直接交付给这一部电梯,即调度器只是将主请求队列中的请求直接搬运到这唯一一部电梯本身的请求队列中。
由于电梯维护自身的请求队列和正在搭载的乘客队列,具体的捎带策略由电梯本身来实现。
关于进程结束的条件,调度器在输入线程结束且主请求队列为空时结束,电梯进程在输入进程结束,自身的请求队列为空且未搭载乘客时结束。
(3)第三阶段电梯
由于第二阶段电梯的铺垫,第三阶段电梯我几乎没有做架构修改,仅仅在调度器线程增加了对三部电梯的管理。
对于换乘的实现我是这样处理的:首先由调度器判断这个请求是不是一个需要换乘的请求。如果需要换乘,将这个请求的换乘标志位置位,并增加中间楼层。接受到换乘标志为1请求的电梯,仅仅实现这个请求由起始楼层到中间楼层的运送,然后生成一个新的请求(中间楼层->目的楼层)并送回主请求队列。
判读调度器线程结束的条件与第二次有了不同,由于运行着的电梯也有可能向主请求队列增加请求,所以输入线程结束和主请求队列为空并不能代表调度器线程结束。所以调度器线程的结束标志是,输入线程结束,并且三部电梯每部电梯的请求队列为空且所搭载的人为空。而电梯线程结束的标志是调度器线程结束。
三、程序分析
(1)第一阶段电梯
代码量
SourceFile | Total Lines | Source Code Lines | source code lines% | coment line counts | comment line counts% | blank | blank lines count% |
---|---|---|---|---|---|---|---|
Lift.java | 77 | 62 | 0.805194805 | 4 | 0.051948052 | 11 | 0.142857143 |
Dispatcher.java | 51 | 38 | 0.745098039 | 2 | 0.039215686 | 11 | 0.215686275 |
Request.java | 34 | 26 | 0.764705882 | 2 | 0.058823529 | 6 | 0.176470588 |
Main.java | 25 | 16 | 0.64 | 2 | 0.08 | 7 | 0.28 |
方法分析
method | ev(G) | iv(G) | v(G) |
---|---|---|---|
Dispatcher.Add(PersonRequest) | 1 | 1 | 1 |
Dispatcher.Dispatcher() | 1 | 1 | 1 |
Dispatcher.Empty() | 2 | 1 | 2 |
Dispatcher.Get(int) | 1 | 3 | 3 |
Dispatcher.getDispatcher() | 1 | 1 | 1 |
Dispatcher.Remove(int) | 1 | 1 | 1 |
Dispatcher.Size() | 1 | 1 | 1 |
Lift.run() | 4 | 11 | 12 |
Main.main(String[]) | 1 | 2 | 2 |
Request.isEnd() | 2 | 1 | 2 |
Request.Request(ElevatorInput) | 1 | 1 | 1 |
Request.run() | 3 | 3 | 3 |
Total | 19 | 27 | 30 |
Average | 1.583333333 | 2.25 | 2.5 |
类图
(2)第二阶段电梯
代码量
SourceFile | Total Lines | Source Code Lines | source code lines% | coment line counts | comment line counts% | blank | blank lines count% |
---|---|---|---|---|---|---|---|
Dispatcher.java | 51 | 39 | 0.764705882 | 3 | 0.058823529 | 9 | 0.176470588 |
Lift.java | 292 | 250 | 0.856164384 | 15 | 0.051369863 | 27 | 0.092465753 |
Main.java | 20 | 16 | 0.8 | 0 | 0 | 4 | 0.2 |
Queue.java | 38 | 27 | 0.710526316 | 1 | 0.026315789 | 10 | 0.263157895 |
方法分析
method | ev(G) | iv(G) | v(G) |
---|---|---|---|
Dispatcher.Dispatcher(Object,Object,Lift) | 1 | 1 | 1 |
Dispatcher.run() | 5 | 7 | 8 |
Lift.Add(PersonRequest) | 1 | 1 | 1 |
Lift.CloseDoor() | 1 | 2 | 2 |
Lift.fooForEmpty() | 1 | 4 | 5 |
Lift.fooForExist() | 3 | 3 | 6 |
Lift.ifIn() | 2 | 10 | 11 |
Lift.ifIn2() | 1 | 9 | 9 |
Lift.ifOpen() | 6 | 7 | 9 |
Lift.ifOpen2() | 6 | 9 | 11 |
Lift.ifOut() | 1 | 3 | 3 |
Lift.Lift(Object) | 1 | 1 | 1 |
Lift.LiftRun() | 1 | 2 | 5 |
Lift.OpenDoor() | 1 | 2 | 2 |
Lift.run() | 5 | 10 | 12 |
Main.main(String[]) | 1 | 1 | 1 |
Queue.Add(PersonRequest) | 1 | 1 | 1 |
Queue.Empty() | 2 | 1 | 2 |
Queue.Get(int) | 1 | 1 | 1 |
Queue.getQueue() | 1 | 1 | 1 |
Queue.Queue() | 1 | 1 | 1 |
Queue.Remove(int) | 1 | 1 | 1 |
Request.isEnd() | 2 | 1 | 2 |
Request.Request(ElevatorInput,Object) | 1 | 1 | 1 |
Request.run() | 3 | 3 | 3 |
Total | 50 | 83 | 100 |
类图
(3)第三阶段电梯
代码量
SourceFile | Total Lines | Source Code Lines | source code lines% | coment line counts | comment line counts% | blank | blank lines count% |
---|---|---|---|---|---|---|---|
Dispatcher.java | 218 | 165 | 0.756880734 | 27 | 0.123853211 | 26 | 0.119266055 |
Lift.java | 417 | 361 | 0.865707434 | 14 | 0.033573141 | 42 | 0.100719424 |
Main.java | 38 | 32 | 0.842105263 | 0 | 0 | 6 | 0.157894737 |
MyPersonRequest.java | 50 | 39 | 0.78 | 1 | 0.02 | 10 | 0.2 |
方法分析
Dispatcher.compare(int,int,int,int) | 6 | 7 | 9 |
---|---|---|---|
Dispatcher.dispatchReq(MyPersonRequest) | 6 | 4 | 11 |
Dispatcher.isEnd() | 1 | 1 | 1 |
Dispatcher.notifyAllLift() | 1 | 1 | 1 |
Dispatcher.proceedSpilt(MyPersonRequest,int) | 2 | 2 | 5 |
Dispatcher.proceedSpiltA(MyPersonRequest) | 1 | 2 | 2 |
Dispatcher.proceedSpiltB(MyPersonRequest) | 1 | 4 | 4 |
Dispatcher.run() | 5 | 7 | 9 |
Lift.Add(MyPersonRequest) | 1 | 1 | 1 |
Lift.canPick(int,int) | 4 | 6 | 10 |
Lift.carryItself(int) | 2 | 1 | 2 |
Lift.carryItself(int,int) | 2 | 2 | 3 |
Lift.CloseDoor() | 1 | 2 | 2 |
Lift.fooForEmpty() | 1 | 6 | 7 |
Lift.fooForExist() | 3 | 3 | 6 |
Lift.getmyName() | 1 | 1 | 1 |
Lift.getmyState() | 1 | 1 | 1 |
Lift.getSpeed() | 1 | 1 | 1 |
Lift.ifIn() | 2 | 11 | 12 |
Lift.ifIn2() | 1 | 10 | 10 |
Lift.ifOpen() | 7 | 8 | 10 |
Lift.ifOpen2() | 7 | 10 | 12 |
Lift.ifOut() | 1 | 5 | 5 |
Lift.isBothEmpty() | 2 | 1 | 2 |
Lift.isEmpty() | 1 | 1 | 1 |
Lift.isReqSizeMax() | 1 | 1 | 1 |
Lift.Lift(Object,Object,int,int,ArrayList,String) | 1 | 1 | 1 |
Lift.LiftRun() | 1 | 2 | 5 |
Lift.OpenDoor() | 1 | 2 | 2 |
Lift.run() | 5 | 6 | 8 |
Main.main(String[]) | 1 | 1 | 1 |
MyPersonRequest.getFromFloor() | 1 | 1 | 1 |
MyPersonRequest.getMiddle() | 1 | 1 | 1 |
MyPersonRequest.getPersonId() | 1 | 1 | 1 |
MyPersonRequest.getRealToFloor() | 1 | 1 | 1 |
MyPersonRequest.getSplitFlag() | 1 | 1 | 1 |
MyPersonRequest.getToFloor() | 2 | 1 | 2 |
MyPersonRequest.MyPersonRequest(int,int,int) | 1 | 1 | 1 |
MyPersonRequest.setIfsplit() | 1 | 1 | 1 |
MyPersonRequest.setMiddle(int) | 1 | 1 | 1 |
Queue.Add(MyPersonRequest) | 1 | 1 | 1 |
Queue.Empty() | 2 | 1 | 2 |
Queue.Get(int) | 1 | 1 | 1 |
Queue.getQueue() | 1 | 1 | 1 |
Queue.Queue() | 1 | 1 | 1 |
Queue.Remove(int) | 1 | 1 | 1 |
Request.isEnd() | 2 | 1 | 2 |
Request.Request(ElevatorInput,Object) | 1 | 1 | 1 |
Request.run() | 3 | 3 | 3 |
Total | 96 | 135 | 173 |
Average | 1.882352941 | 2.647058824 | 3.392156863 |
类图
四、Bug分析
第一次作业,由于电梯的调度策略比较简单(FAFS,先来先服务),在重点考率了各个进程结束条件的关系后,中测和强测试都没有出现bug。
第二次作业,中测没有出现bug。但是由于我在电梯进行方向转换时,没有进行电梯运行参数的重置,导致重大bug,这个锅还是得怪自己私下测试不够全面。
第三次作业,由于增加了换乘需求,导致前几次中测,由于进程结束问题的bug没有通过。强测时,我居然在main函数中将C电梯停靠的楼层初始化错误(多打了一个10层),而且中测和我自己私下测试居然都没有发现这个bug(傻了),今后还是得完备自己的测试集啊。
五、SOLID分析
名称 | 含义 |
---|---|
SRP | 单一责任原则 |
OCP | 开放封闭原则 |
LSP | 里氏替换原则 |
DIP | 依赖倒置原则 |
ISP | 接口分离原则 |
SRP原则分析: | |
请求类、请求队列、调度器类、电梯类,各个类的职责区分的非常清楚,输入线程、调度器线程、电梯线程都能找到对应的类。 | |
OCP原则分析: | |
由于三次作业的电梯运行策略都不同,依次增加捎带策略、人数上限、停靠楼层限制,我对电梯类的内部方法修改量还是挺大的。 | |
LSP原则、DIP原则分析、ISP原则分析: | |
对于电梯系统的实现,我的设计模式没有继承关系的存在,所以我没有进行抽象和继承。 | |
同样的,对于输入线程、调度线程和电梯线程,在我对设计中,它们各自方法的独立性较强,所以我没有设计接口来解决它们之间可能存在相似性的方法。 |
六、心得体会
从对多线程编程一无所知,经过三次电梯作业的历练,我对多线程编程逐步有了自己的理解。当多个线程之间对同一对象进行WW(WRITE,WRITE),WR(WRITE,READ)操作时,我们应该对共享对象加锁,实现进程间的同步访问,保证操作的原子性;同时wait,notify策略比轮询策略更好,能有效降低cpu运行时间。希望,在今后的oo课程中能够进一步加深我对多线程编程的理解和运用。