第二单元的主要内容是面向对象程序设计中多线程的协同和同步控制。在这一目标下,第五、六、七次作业以电梯为研究对象,考察了多线程设计能力。
一、第五次作业
第五次作业的要求是实现一个目的选层电梯将客人运送到指定楼层。我的设计策略是主函数不断读取PersonRequest存入InformationPool中,再实现一个电梯线程不断地从InformationPool中读取需要运送的乘客信息,实现接送。为便于后续作业的多电梯设计,InformationPool中电梯读入数据的函数都使用synchronized关键字上了锁。为便于后续优化,将每层的所有乘客按照上行和下行分为两个队列并按照目的楼层升序排列。
度量分析:
方法:
Elevator.addPersons(ArrayList) | 1.0 | 2.0 | 2.0 |
Elevator.close() | 1.0 | 2.0 | 2.0 |
Elevator.Elevator(int,InformationPool) | 1.0 | 1.0 | 1.0 |
Elevator.goTo(int) | 1.0 | 2.0 | 2.0 |
Elevator.open() | 1.0 | 2.0 | 2.0 |
Elevator.personsIn(int) | 1.0 | 3.0 | 3.0 |
Elevator.personsOut() | 4.0 | 6.0 | 7.0 |
Elevator.run() | 1.0 | 4.0 | 4.0 |
Elevator.selectNextStop() | 4.0 | 4.0 | 4.0 |
ElevatorManager.main(String[]) | 3.0 | 3.0 | 3.0 |
Floor.addPerson(Person) | 1.0 | 2.0 | 2.0 |
Floor.elevatorDown() | 1.0 | 1.0 | 1.0 |
Floor.elevatorUp() | 1.0 | 1.0 | 1.0 |
Floor.Floor(int) | 1.0 | 1.0 | 1.0 |
Floor.needElevatorDown() | 1.0 | 1.0 | 1.0 |
Floor.needElevatorUp() | 1.0 | 1.0 | 1.0 |
InformationPool.ElevatorDown(int,int) | 1.0 | 1.0 | 1.0 |
InformationPool.ElevatorUp(int,int) | 1.0 | 1.0 | 1.0 |
InformationPool.floorsNeedDown() | 1.0 | 3.0 | 3.0 |
InformationPool.floorsNeedUp() | 1.0 | 3.0 | 3.0 |
InformationPool.InformationPool() | 1.0 | 1.0 | 2.0 |
InformationPool.isStopped() | 1.0 | 1.0 | 1.0 |
InformationPool.newPerson(int,int,int) | 1.0 | 1.0 | 1.0 |
InformationPool.stop() | 1.0 | 1.0 | 1.0 |
Person.getId() | 1.0 | 1.0 | 1.0 |
Person.getToFloor() | 1.0 | 1.0 | 1.0 |
Person.Person(int,int) | 1.0 | 1.0 | 1.0 |
tests.SelfTestMainClassForTimableOutput.main(String[]) | 1.0 | 1.0 | 1.0 |
tests.TestMain.main(String[]) | 3.0 | 3.0 | 3.0 |
Utils.addPerson(Person,ArrayList) | 1.0 | 2.0 | 3.0 |
Utils.addPersons(ArrayList,ArrayList) | 1.0 | 8.0 | 9.0 |
Total | 41.0 | 65.0 | 69.0 |
Average | 1.3225806451612903 | 2.096774193548387 | 2.225806451612903 |
类:
Elevator | 2.5555555555555554 | 23.0 |
ElevatorManager | 3.0 | 3.0 |
Floor | 1.1666666666666667 | 7.0 |
InformationPool | 1.625 | 13.0 |
Person | 1.0 | 3.0 |
tests.SelfTestMainClassForTimableOutput | 1.0 | 1.0 |
tests.TestMain | 3.0 | 3.0 |
Utils | 3.0 | 6.0 |
Total | 59.0 | |
Average | 1.903225806451613 | 7.375 |
UML类图:
UML 时序图:
SOLID:
SRP(The Single Responsibility Principle)单一责任原则:符合单一责任原则:Person负责记录单个PersonRequest,Floor负责管理统一出发层的Person,InformationPool负责整合Floor信息,Elevator负责实现电梯线程,Utils负责提供静态工具函数。
OCP(The Open Closed Principle)开放封闭原则:没有封装后更改,各个类都具备可拓展性。
LSP(The Liskov Substitution Principle)里氏替换原则:除Elevator继承Thread类,未使用继承。
ISP(The Interface Seqreqation Principle)接口分离原则:未使用接口。
DIP(The Dependency Inversion Principle)依赖倒置原则:由于未使用抽象接口,仍然依赖于实现进行编程。但本次作业在实现中考虑到了日后可能出现的新需求,故第六、七次作业均未大幅改动。
BUG:
在公测中未发现BUG。后来经自己分析认为可能出现的bug是主线程在读完PersonRequest停止后Elevator线程可能提前停止的问题,但由于测试样例较少未发生该问题。
二、第六次作业
第六次作业加入了负楼层,并推荐使用ALS(可捎带电梯)调度策略。我的设计策略是使电梯每到一层都检查有无到达当前层的乘客,并检查InformationPool中当前层有无目标方向与当前运行方向相同的新乘客,并建立一个目的地序列,以更早进入电梯的乘客的目的地为更优先目标。
度量分析:
方法:
InformationPool.newPerson(int,int,int) | 1.0 | 1.0 | 1.0 |
InformationPool.stop() | 1.0 | 1.0 | 1.0 |
Person.getId() | 1.0 | 1.0 | 1.0 |
Person.getToFloor() | 1.0 | 1.0 | 1.0 |
Person.Person(int,int) | 1.0 | 1.0 | 1.0 |
Utils.addPerson(Person,ArrayList) | 1.0 | 2.0 | 3.0 |
Utils.addPersons(ArrayList,ArrayList) | 1.0 | 8.0 | 9.0 |
Utils.distance(int,int) | 2.0 | 2.0 | 2.0 |
Total | 43.0 | 75.0 | 80.0 |
Average | 1.2647058823529411 | 2.2058823529411766 | 2.3529411764705883 |
类:
Elevator | 2.8333333333333335 | 34.0 |
ElevatorManager | 3.0 | 3.0 |
Floor | 1.1666666666666667 | 7.0 |
InformationPool | 1.6666666666666667 | 15.0 |
Person | 1.0 | 3.0 |
Utils | 2.6666666666666665 | 8.0 |
Total | 70.0 | |
Average | 2.0588235294117645 | 11.666666666666666 |
UML类图:
UML时序图:
SOLID:
SRP(The Single Responsibility Principle)单一责任原则:符合单一责任原则:Person负责记录单个PersonRequest,Floor负责管理统一出发层的Person,InformationPool负责整合Floor信息,Elevator负责实现电梯线程,Utils负责提供静态工具函数。
OCP(The Open Closed Principle)开放封闭原则:没有封装后更改,各个类都具备可拓展性。
LSP(The Liskov Substitution Principle)里氏替换原则:除Elevator继承Thread类,未使用继承。
ISP(The Interface Seqreqation Principle)接口分离原则:未使用接口。
DIP(The Dependency Inversion Principle)依赖倒置原则:由于未使用抽象接口,仍然依赖于实现进行编程。
BUG:
出现了主线程在读完PersonRequest停止后Elevator线程可能提前停止的问题,通过主线程延迟停止解决了该问题。同时还出现了超出CPU时间限制的问题,这是由于Elevator线程在没有乘客时不断检查InformationPool导致的。尝试使用工作完等待和从主线程唤醒的方法解决该问题,但评测结果与自测不一致难以调试,因此替换为Elevator线程在没有乘客时每隔1毫秒检查一次InformationPool。由于本次作业性能分基于超出平均时间几个标准差来计算,而大多数同学在大多数公测测试点上都达到了策略最优解导致标准差为毫秒级别,因此该操作导致我丢失了一些性能分。
三、第七次作业
第七次作业设定了三台电梯,并且每台电梯可到达楼层与运行速度均不同,因此需要通过线程(电梯)间合作来满足部分乘客的需求。我的设计策略是读入一个PersonRequest后通过动态规划解出对该乘客耗时最短的运输策略,并压成栈存入Person实体中,电梯通过检查栈是否为空可判断乘客是否到达目的地,若没有到达目的地则将乘客放回InformationPool等待其他电梯继续运送该乘客。同时为缩短乘客在中转楼层的等待时间,InformationPool中维护了一张中转站表,电梯在没有乘客时可根据该表查询到自己需要接乘客的中转站并提前到此等待。为便于检视乘客信息,在InformationPool中同时维护了一个根据id记录乘客的HashMap。
度量分析:
方法:
Elevator.addPersons(ArrayList) | 1.0 | 2.0 | 2.0 |
Elevator.close() | 1.0 | 1.0 | 1.0 |
Elevator.Elevator(String,InformationPool) | 1.0 | 1.0 | 1.0 |
Elevator.goTo(int) | 1.0 | 5.0 | 7.0 |
Elevator.open() | 1.0 | 1.0 | 1.0 |
Elevator.personsIn(int) | 1.0 | 3.0 | 3.0 |
Elevator.personsOut() | 4.0 | 7.0 | 8.0 |
Elevator.run() | 1.0 | 7.0 | 7.0 |
Elevator.selectNextStop() | 4.0 | 6.0 | 6.0 |
Elevator.trySleep(int) | 1.0 | 2.0 | 2.0 |
ElevatorManager.main(String[]) | 3.0 | 3.0 | 3.0 |
Floor.addPerson(Person) | 1.0 | 2.0 | 2.0 |
Floor.elevatorDown(String,int) | 1.0 | 1.0 | 1.0 |
Floor.elevatorUp(String,int) | 1.0 | 1.0 | 1.0 |
Floor.Floor(int) | 1.0 | 2.0 | 2.0 |
Floor.needElevatorDown(String) | 1.0 | 1.0 | 1.0 |
Floor.needElevatorUp(String) | 1.0 | 1.0 | 1.0 |
InformationPool.ElevatorDown(int,int,String) | 1.0 | 1.0 | 1.0 |
InformationPool.ElevatorUp(int,int,String) | 1.0 | 1.0 | 1.0 |
InformationPool.floorsNeedDown(String) | 1.0 | 3.0 | 3.0 |
InformationPool.floorsNeedUp(String) | 1.0 | 3.0 | 3.0 |
InformationPool.InformationPool() | 1.0 | 2.0 | 2.0 |
InformationPool.isStopped() | 1.0 | 1.0 | 1.0 |
InformationPool.needElevatorDown(int,String) | 1.0 | 1.0 | 1.0 |
InformationPool.needElevatorUp(int,String) | 1.0 | 1.0 | 1.0 |
InformationPool.needToWait(int,String) | 1.0 | 1.0 | 1.0 |
InformationPool.newPerson(int,Person,boolean) | 1.0 | 2.0 | 2.0 |
InformationPool.stop() | 1.0 | 4.0 | 4.0 |
Person.arriveDest() | 1.0 | 1.0 | 1.0 |
Person.getElevatorId() | 1.0 | 1.0 | 1.0 |
Person.getId() | 1.0 | 1.0 | 1.0 |
Person.getToFloor() | 1.0 | 1.0 | 1.0 |
Person.nextToFloor() | 1.0 | 1.0 | 1.0 |
Person.Person(int,Stack,Stack) | 1.0 | 1.0 | 1.0 |
Utils.add(Person,ArrayList) | 1.0 | 2.0 | 3.0 |
Utils.calcTime(int,int,String) | 3.0 | 3.0 | 3.0 |
Utils.distance(int,int) | 2.0 | 2.0 | 2.0 |
Utils.getElevatorIds() | 1.0 | 1.0 | 1.0 |
Utils.getFloors() | 1.0 | 1.0 | 1.0 |
Utils.getMaxPassengers(String) | 3.0 | 2.0 | 3.0 |
Utils.getMsForClose() | 1.0 | 1.0 | 1.0 |
Utils.getMsForOpen() | 1.0 | 1.0 | 1.0 |
Utils.getMsPerFloor(String) | 4.0 | 3.0 | 4.0 |
Utils.getWaitInfo(String) | 2.0 | 2.0 | 2.0 |
Utils.initialize() | 1.0 | 8.0 | 8.0 |
Utils.main(String[]) | 1.0 | 2.0 | 2.0 |
Utils.mainThreadTrySleep(int) | 1.0 | 2.0 | 2.0 |
Utils.move(ArrayList,int) | 1.0 | 2.0 | 3.0 |
Utils.newPerson(int,int,int) | 1.0 | 12.0 | 13.0 |
Total | 66.0 | 116.0 | 124.0 |
Average | 1.346938775510204 | 2.36734693877551 | 2.5306122448979593 |
类:
Elevator | 3.5 | 35.0 |
ElevatorManager | 3.0 | 3.0 |
Floor | 1.3333333333333333 | 8.0 |
InformationPool | 1.8181818181818181 | 20.0 |
Person | 1.0 | 6.0 |
Utils | 3.066666666666667 | 46.0 |
Total | 118.0 | |
Average | 2.4081632653061225 | 19.666666666666668 |
UML类图:
UML时序图:
SOLID:
SRP(The Single Responsibility Principle)单一责任原则:符合单一责任原则:Person负责记录单个PersonRequest,Floor负责管理统一出发层的Person,InformationPool负责整合Floor信息,Elevator负责实现电梯线程,Utils负责提供静态工具函数。
OCP(The Open Closed Principle)开放封闭原则:没有封装后更改,各个类都具备可拓展性。
LSP(The Liskov Substitution Principle)里氏替换原则:除Elevator继承Thread类,未使用继承。
ISP(The Interface Seqreqation Principle)接口分离原则:未使用接口。
DIP(The Dependency Inversion Principle)依赖倒置原则:由于未使用抽象接口,仍然依赖于实现进行编程。
BUG:
很遗憾部分强测测试点没有通过。原因是:读入PersonRequest新建Person后不仅存入了Floor容器中,还(将指针)存入了InformationPool中用于监视状态。由于在InformationPool中是使用以id为索引的HashMap存储,在id(Integer)较大时put()操作可能较慢,若在put()完成前被Elevator询问则会出现空指针错误。
四、心得体会:
电梯部分作业是我第一次接触多线程,这几次作业让我熟悉了多线程的协同和同步控制,并且认识到多线程程序的调试困难。以后在面对多线程任务时,我应该更加仔细地分析与验证时序协作设计,避免出现第三次作业类似的严重问题。