三次作业都采用生产者消费者的设计模式,电梯调度用了look算法。在第一次作业中,主要有电梯和请求模拟器两个线程。我在第一次作业的设计中加入了调度器,主要负责电梯和请求队列的交互,即在何时的时机从请求队列中取得请求放入调度器的内部队列中,并给电梯发送指令,故电梯运行的逻辑大部分放在调度器中,上、下、开门、关门、上客、下客,都由调度器决定。这样设计的初衷是使电梯线程的逻辑更加简洁,电梯线程只需要从调度器获取指令并照做就行。在第二次作业中,由于有多部电梯,需要分两层调度。第一次作业的调度器事实上维护了电梯内部队列,还需要一个总调度器,用于将请求分配给不同的电梯,故在第二次作业中,加入了总调度器线程,一旦请求队列有请求,就分配给电梯的内部队列。第二次作业对look
算法的理解更深入了,所以对第一次作业进行了比较大的重构,将第一次作业的内外队列合并为一个队列,使得单部电梯的逻辑更为简洁,为此,电梯需要与内部队列交互,电梯的逻辑变得更加复杂。所以第二次作业有请求模拟器线程、总调度器线程、和若干电梯线程。对于请求队列来说,请求模拟器线程是生产者,总调度器线程是消费者;对于内部队列来说,总调度器线程是生产者,电梯线程是消费者。第三次作业与第二次作业类似,不同之处在于,对于转乘的请求,首先由总调度器将其拆分为两个请求,然后以乘客为元素发送给电梯的内部队列,当乘客离开电梯时,电梯作为生产者,将下一个请求发送给请求队列。
对于多线程的同步控制,我遇到的难点在于结束所有的线程。对于一组生产者消费者模式的实例,生产者的线程结束时,消费者可能处于请求队列get
方法的wait
中,无法自动结束。我的处理方法是,在生产者线程即将结束时,调用请求队列中的endProducer
方法,这个方法首先将请求队列中的producerEnd
属性置为true
,然后notifyAll()
,用来唤醒正在wait
的进程。在get
方法的wait
的条件中加入&& !producerEnd
,一旦生产者的线程结束,便停止wait
,返回一个能够识别的返回值,如null
。消费者接受到这个特殊的标志后结束消费者的线程。
可扩展性分析
在第三次作业中,我主要关注功能设计,但从最后强测结果来看,性能也还不错。第三次作业中的总调度器主要采取如下策略:
-
将每一个请求分为可直达请求和转乘请求
-
可直达请求分配给电梯型号的优先级为C A B,即如果ABC型号的电梯都能满足直达请求,优先分给C型号,然后A型号,最后B型号。这样考虑的理由是C型号停靠的楼层最少,在请求完全随机的情况下,C直达的请求是最少的。
-
转乘请求最多只转乘一次
-
转乘请求的转乘楼层需要满足乘客乘坐电梯经过的层数最少,即路程最少
-
直接将转乘请求拆分为两个请求,然后分配给第一个请求对应的电梯型号
-
如果存在多个型号相同的电梯,则随机分配给一个电梯
总体来说,没有因为过分追求性能而降低可扩展性,功能设计与性能设计的平衡性较好。事实上,对于look算法,由于输入的不确定性,要想在性能上做出显著的优化是很困难的。我的总调度器并没有对期望的时间进行任何的计算,也没有获得任何电梯的运行信息,只是做了一个简单规则与随机因素结合的分配策略,剩下的留给电梯线程自己去跑。
由于第二次作业进行了比较大的重构,第三次作业的可扩展性已经有比较大的提升。
单一职责原则(Single Responsibility Principle)
这次作业中,我引入了很多的类,将功能细化,职责进行分派。比如将电梯类中的内部队列,轿厢,门,型号信息独立成类,然后再组装成电梯。存在缺陷的是总调度器类,其中有将近一半都在做请求的分类,拆分的事,其实将请求的解析和拆分放在一个独立的类里会更好,这是我在设计时没有考虑周全的地方。
开闭原则(Open Closed Principle)
这是我觉得我欠缺的地方,也是我觉得比较难做到的地方。每次遇到实际问题时,往往会直接上手,缺少抽象的过程,导致代码的灵活性受限,随意我常常遇到某一部分要重构的问题。第三次作业由于不同的类功能分开,一些类的职责较为确定,所以开闭原则比前面的作业要更加满足。
里氏替换原则(Liskov Substitution Principle)
这次作业我设计了电梯类型这个父类,ABC三种电梯类型构成子类,没有重写父类的方法,所有的使用场景都用的子类,所以这条原则得到满足。
迪米特法则(Law of Demeter)
由于采用生产者消费者模式,所以说满足迪米特法则。
接口隔离原则(Interface Segregation Principle)
生产者消费者模式中,类之间只进行必要的交流,没有多余的接口,基本满足接口隔离原则。
依赖倒置原则(Dependence Inversion Principle)
我的理解是依赖倒置原则强调抽象,在抽象间进行交互,在本单元作业的情境下没有具体的体现。
程序结构分析
第三次作业的分析如下
class | OCavg | WMC |
---|---|---|
elevator.debug.MyDebug | 1.0 | 3.0 |
elevator.elevator.AnsweredQueue | 3.125 | 25.0 |
elevator.elevator.Container | 2.0 | 18.0 |
elevator.elevator.Direction | 2.0 | 2.0 |
elevator.elevator.Door | 1.0 | 4.0 |
elevator.elevator.Elevator | 2.1666666666666665 | 39.0 |
elevator.elevator.elevatortype.ElevatorType | 1.0 | 11.0 |
elevator.elevator.elevatortype.ElevatorTypeA | 1.0 | 1.0 |
elevator.elevator.elevatortype.ElevatorTypeB | 1.0 | 1.0 |
elevator.elevator.elevatortype.ElevatorTypeC | 1.0 | 1.0 |
elevator.elevator.Factory | 4.0 | 4.0 |
elevator.elevator.SubQueue | 1.2727272727272727 | 14.0 |
elevator.ElevatorPool | 2.0833333333333335 | 25.0 |
elevator.end.End | 1.0 | 7.0 |
elevator.Floor | 1.2857142857142858 | 27.0 |
elevator.MainClass | 5.0 | 5.0 |
elevator.Passenger | 1.2 | 12.0 |
elevator.RequestType | 1.0 | 20.0 |
elevator.Scheduler | 5.5 | 44.0 |
elevator.UnansweredQueue | 1.3333333333333333 | 8.0 |
Total | 271.0 | |
Average | 1.7597402597402598 | 13.55 |
method | ev(G) | iv(G) | v(G) |
---|---|---|---|
elevator.debug.MyDebug.debug() | 1.0 | 1.0 | 1.0 |
elevator.debug.MyDebug.Elevator() | 1.0 | 1.0 | 1.0 |
elevator.debug.MyDebug.Scheduler() | 1.0 | 1.0 | 1.0 |
elevator.elevator.AnsweredQueue.addPassenger(Passenger) | 1.0 | 1.0 | 1.0 |
elevator.elevator.AnsweredQueue.AnsweredQueue(String) | 1.0 | 2.0 | 2.0 |
elevator.elevator.AnsweredQueue.End() | 1.0 | 1.0 | 1.0 |
elevator.elevator.AnsweredQueue.getDirectionWhenIdle(int) | 7.0 | 10.0 | 12.0 |
elevator.elevator.AnsweredQueue.getPassengers(int,Direction,int) | 1.0 | 7.0 | 7.0 |
elevator.elevator.AnsweredQueue.hasGetInRequest(int,Direction) | 1.0 | 1.0 | 1.0 |
elevator.elevator.AnsweredQueue.hasMoreRequestInDirection(int,Direction) | 6.0 | 4.0 | 6.0 |
elevator.elevator.AnsweredQueue.isEmpty() | 1.0 | 1.0 | 1.0 |
elevator.elevator.Container.addPassenger(Passenger) | 1.0 | 1.0 | 1.0 |
elevator.elevator.Container.Container(int) | 1.0 | 1.0 | 1.0 |
elevator.elevator.Container.dropPassengers(int) | 1.0 | 3.0 | 3.0 |
elevator.elevator.Container.getPassengers() | 1.0 | 1.0 | 1.0 |
elevator.elevator.Container.hasDropRequest(int) | 3.0 | 2.0 | 3.0 |
elevator.elevator.Container.isEmpty() | 1.0 | 1.0 | 1.0 |
elevator.elevator.Container.isFull() | 1.0 | 1.0 | 1.0 |
elevator.elevator.Container.margin() | 1.0 | 1.0 | 1.0 |
elevator.elevator.Container.noMoreRequestInDirection(int,Direction) | 6.0 | 4.0 | 6.0 |
elevator.elevator.Direction.reverse(Direction) | 2.0 | 1.0 | 2.0 |
elevator.elevator.Door.Close() | 1.0 | 2.0 | 2.0 |
elevator.elevator.Door.Door(int,int) | 1.0 | 1.0 | 1.0 |
elevator.elevator.Door.isOpen() | 1.0 | 1.0 | 1.0 |
elevator.elevator.Door.Open() | 1.0 | 2.0 | 2.0 |
elevator.elevator.Elevator.acceptPassenger(Passenger) | 1.0 | 1.0 | 1.0 |
elevator.elevator.Elevator.Close() | 1.0 | 1.0 | 1.0 |
elevator.elevator.Elevator.Down() | 1.0 | 3.0 | 3.0 |
elevator.elevator.Elevator.dropAll() | 1.0 | 3.0 | 3.0 |
elevator.elevator.Elevator.Elevator(String,ElevatorType,UnansweredQueue) | 1.0 | 1.0 | 1.0 |
elevator.elevator.Elevator.End() | 1.0 | 1.0 | 1.0 |
elevator.elevator.Elevator.getDirection() | 1.0 | 1.0 | 1.0 |
elevator.elevator.Elevator.getFloor() | 1.0 | 1.0 | 1.0 |
elevator.elevator.Elevator.getLabel() | 1.0 | 1.0 | 1.0 |
elevator.elevator.Elevator.getType() | 1.0 | 1.0 | 1.0 |
elevator.elevator.Elevator.Idle() | 1.0 | 1.0 | 1.0 |
elevator.elevator.Elevator.isFull() | 1.0 | 1.0 | 1.0 |
elevator.elevator.Elevator.isIdle() | 1.0 | 2.0 | 2.0 |
elevator.elevator.Elevator.loadPerson(Passenger) | 1.0 | 1.0 | 1.0 |
elevator.elevator.Elevator.Move() | 1.0 | 2.0 | 2.0 |
elevator.elevator.Elevator.Open() | 1.0 | 1.0 | 1.0 |
elevator.elevator.Elevator.run() | 7.0 | 15.0 | 17.0 |
elevator.elevator.Elevator.Up() | 1.0 | 3.0 | 3.0 |
elevator.elevator.elevatortype.ElevatorType.canReach(int) | 1.0 | 1.0 | 1.0 |
elevator.elevator.elevatortype.ElevatorType.ElevatorType(ArrayList,int,int,String) | 1.0 | 1.0 | 1.0 |
elevator.elevator.elevatortype.ElevatorType.getCapacity() | 1.0 | 1.0 | 1.0 |
elevator.elevator.elevatortype.ElevatorType.getCloseTime() | 1.0 | 1.0 | 1.0 |
elevator.elevator.elevatortype.ElevatorType.getDefaultFloor() | 1.0 | 1.0 | 1.0 |
elevator.elevator.elevatortype.ElevatorType.getFloors() | 1.0 | 1.0 | 1.0 |
elevator.elevator.elevatortype.ElevatorType.getLowerFloor(int) | 1.0 | 1.0 | 1.0 |
elevator.elevator.elevatortype.ElevatorType.getMoveTime() | 1.0 | 1.0 | 1.0 |
elevator.elevator.elevatortype.ElevatorType.getOpenTime() | 1.0 | 1.0 | 1.0 |
elevator.elevator.elevatortype.ElevatorType.getType() | 1.0 | 1.0 | 1.0 |
elevator.elevator.elevatortype.ElevatorType.getUpperFloor(int) | 1.0 | 1.0 | 1.0 |
elevator.elevator.elevatortype.ElevatorTypeA.ElevatorTypeA() | 1.0 | 1.0 | 1.0 |
elevator.elevator.elevatortype.ElevatorTypeB.ElevatorTypeB() | 1.0 | 1.0 | 1.0 |
elevator.elevator.elevatortype.ElevatorTypeC.ElevatorTypeC() | 1.0 | 1.0 | 1.0 |
elevator.elevator.Factory.getElevator(String,String,UnansweredQueue) | 4.0 | 4.0 | 4.0 |
elevator.elevator.SubQueue.addPassenger(Passenger) | 1.0 | 2.0 | 2.0 |
elevator.elevator.SubQueue.hasDirectionRequest(Direction) | 2.0 | 2.0 | 2.0 |
elevator.elevator.SubQueue.hasDownPassengers() | 1.0 | 1.0 | 1.0 |
elevator.elevator.SubQueue.hasUpPassengers() | 1.0 | 1.0 | 1.0 |
elevator.elevator.SubQueue.isDownPassenger(Passenger) | 1.0 | 1.0 | 1.0 |
elevator.elevator.SubQueue.isEmpty() | 1.0 | 1.0 | 1.0 |
elevator.elevator.SubQueue.isUpPassenger(Passenger) | 1.0 | 1.0 | 1.0 |
elevator.elevator.SubQueue.popDirectionPassenger(Direction) | 2.0 | 2.0 | 2.0 |
elevator.elevator.SubQueue.popDownPassenger() | 1.0 | 1.0 | 1.0 |
elevator.elevator.SubQueue.popUpPassenger() | 1.0 | 1.0 | 1.0 |
elevator.elevator.SubQueue.SubQueue(int) | 1.0 | 1.0 | 1.0 |
elevator.ElevatorPool.add(Elevator) | 1.0 | 4.0 | 4.0 |
elevator.ElevatorPool.areIdle() | 7.0 | 4.0 | 7.0 |
elevator.ElevatorPool.ElevatorPool() | 1.0 | 1.0 | 1.0 |
elevator.ElevatorPool.getACount() | 1.0 | 1.0 | 1.0 |
elevator.ElevatorPool.getBCount() | 1.0 | 1.0 | 1.0 |
elevator.ElevatorPool.getCCount() | 1.0 | 1.0 | 1.0 |
elevator.ElevatorPool.getElevatorAs() | 1.0 | 1.0 | 1.0 |
elevator.ElevatorPool.getElevatorBs() | 1.0 | 1.0 | 1.0 |
elevator.ElevatorPool.getElevatorCs() | 1.0 | 1.0 | 1.0 |
elevator.ElevatorPool.getElevators() | 1.0 | 1.0 | 1.0 |
elevator.ElevatorPool.runAll() | 1.0 | 4.0 | 4.0 |
elevator.ElevatorPool.setElevatorsStatus() | 1.0 | 2.0 | 2.0 |
elevator.end.End.inputEnd() | 1.0 | 1.0 | 1.0 |
elevator.end.End.InputIsEnd() | 1.0 | 1.0 | 1.0 |
elevator.end.End.isElevatorsIdle() | 1.0 | 1.0 | 1.0 |
elevator.end.End.schedulerEnd() | 1.0 | 1.0 | 1.0 |
elevator.end.End.schedulerIsEnd() | 1.0 | 1.0 | 1.0 |
elevator.end.End.setElevatorsIdle() | 1.0 | 1.0 | 1.0 |
elevator.end.End.setElevatorsNotIdle() | 1.0 | 1.0 | 1.0 |
elevator.Floor.abCanReach(int) | 1.0 | 1.0 | 1.0 |
elevator.Floor.abcCanReach(int) | 1.0 | 1.0 | 1.0 |
elevator.Floor.aCanReach(int) | 1.0 | 1.0 | 1.0 |
elevator.Floor.acCanReach(int) | 1.0 | 1.0 | 1.0 |
elevator.Floor.bCanReach(int) | 1.0 | 1.0 | 1.0 |
elevator.Floor.bcCanReach(int) | 1.0 | 1.0 | 1.0 |
elevator.Floor.cCanReach(int) | 1.0 | 1.0 | 1.0 |
elevator.Floor.distanceOfFloor(int,int) | 2.0 | 1.0 | 4.0 |
elevator.Floor.getFloorCount() | 1.0 | 1.0 | 1.0 |
elevator.Floor.getFloorsAB() | 1.0 | 1.0 | 1.0 |
elevator.Floor.getFloorsAC() | 1.0 | 1.0 | 1.0 |
elevator.Floor.getFloorsBC() | 1.0 | 1.0 | 1.0 |
elevator.Floor.getLowerFloor(int) | 2.0 | 1.0 | 2.0 |
elevator.Floor.getMaxFloor() | 1.0 | 1.0 | 1.0 |
elevator.Floor.getMaxValue() | 1.0 | 1.0 | 1.0 |
elevator.Floor.getMinFloor() | 1.0 | 1.0 | 1.0 |
elevator.Floor.getMinValue() | 1.0 | 1.0 | 1.0 |
elevator.Floor.getUpperFloor(int) | 2.0 | 1.0 | 2.0 |
elevator.Floor.isValidFloor(int) | 1.0 | 1.0 | 3.0 |
elevator.Floor.keyToValue(int) | 2.0 | 1.0 | 2.0 |
elevator.Floor.valueToKey(int) | 2.0 | 1.0 | 3.0 |
elevator.MainClass.main(String[]) | 3.0 | 5.0 | 5.0 |
elevator.Passenger.equals(Object) | 3.0 | 2.0 | 4.0 |
elevator.Passenger.getFromFloor() | 1.0 | 1.0 | 1.0 |
elevator.Passenger.getPersonId() | 1.0 | 1.0 | 1.0 |
elevator.Passenger.getRequest() | 1.0 | 1.0 | 1.0 |
elevator.Passenger.getToFloor() | 1.0 | 1.0 | 1.0 |
elevator.Passenger.hashCode() | 1.0 | 1.0 | 1.0 |
elevator.Passenger.hasNextRequest() | 1.0 | 1.0 | 1.0 |
elevator.Passenger.Passenger(PersonRequest) | 1.0 | 1.0 | 1.0 |
elevator.Passenger.Passenger(PersonRequest,PersonRequest) | 1.0 | 1.0 | 1.0 |
elevator.Passenger.transfer() | 1.0 | 1.0 | 1.0 |
elevator.RequestType.getFromElevator() | 1.0 | 1.0 | 1.0 |
elevator.RequestType.getToElevator() | 1.0 | 1.0 | 1.0 |
elevator.Scheduler.Dispatch(PersonRequest) | 1.0 | 5.0 | 5.0 |
elevator.Scheduler.findBestInterchangeNumber(ArrayList,int,int) | 2.0 | 6.0 | 6.0 |
elevator.Scheduler.getInterchangeFloor(String,String,int,int) | 4.0 | 13.0 | 13.0 |
elevator.Scheduler.getType(PersonRequest) | 13.0 | 13.0 | 22.0 |
elevator.Scheduler.inOrder(int,int,int) | 1.0 | 1.0 | 4.0 |
elevator.Scheduler.run() | 6.0 | 7.0 | 8.0 |
elevator.Scheduler.Scheduler(UnansweredQueue,ElevatorPool) | 1.0 | 1.0 | 1.0 |
elevator.Scheduler.waitForNewRequest() | 1.0 | 1.0 | 1.0 |
elevator.UnansweredQueue.addRequest(PersonRequest) | 1.0 | 1.0 | 1.0 |
elevator.UnansweredQueue.End() | 1.0 | 1.0 | 1.0 |
elevator.UnansweredQueue.getCurrentNum() | 1.0 | 1.0 | 1.0 |
elevator.UnansweredQueue.getRequest() | 2.0 | 7.0 | 8.0 |
elevator.UnansweredQueue.isEmpty() | 1.0 | 1.0 | 1.0 |
elevator.UnansweredQueue.UnansweredQueue(ElevatorPool) | 1.0 | 1.0 | 1.0 |
null.getFromElevator() | 1.0 | 1.0 | 1.0 |
null.getToElevator() | 1.0 | 1.0 | 1.0 |
Total | 205.0 | 253.0 | 292.0 |
Average | 1.49 | 1.83 | 2.16 |
类图
优点在于每个类的职责明确,类比较多的情况下,一个类的复杂度能得到控制。
缺点在于类之间的交互比较多,有些类的设置使得类之间有很多间接的交互。
UML时序图
bug分析
没有遇到死锁的情况,经过多次改进也消除了线程结束不了的问题。
bug寻找策略
本单元尝试了编写自动测试脚本进行随机测试,在三次作业中均找到了少量bug,具有一定的有效性,但没能发现自己的bug,说明随机下的数据也不能保证程序的正确性。
由于本单元的作业中的线程间同步和安全问题很难基于规则去针对,于是就采用了随机重复的方法,第一单元具有很多的规则,可以根据规则一一检验,往往很有效。而这一次作业则需要反复多次地测试才可能测出一些bug,在测评机中hack也未必能复现。
心得体会
-
线程安全不仅仅是共享对象的访问,实际问题可能非常复杂,没有线程的模板,需要作出具体的分析,不断尝试并验证。具有线程思维真的很重要。
-