模拟多线程实时电梯系统,新主楼ABCDE五个楼座各楼层均有电梯,乘客发起形如“从X座x层到Y座y层”的请求,电梯模拟上下行、开关门、乘客进出等行为,以满足所有乘客的要求。
各个电梯无论是具体行为还是调度请求都相互独立,因此可以采用多线程的设计思想,每个电梯建立一个线程,这样电梯的行为都可以在线程内完成,可以非常方便的使用 sleep, wait, notifyAll
等函数来模拟消耗时间的操作和等待请求,同时输入也可以采用一个线程来处理。
整体的架构方面,三次作业我都采用了生产者-消费者模型,也就是刚才所提,输入作为生产者写为线程,该线程不断读取输入并提供给托盘;电梯作为消费者处理托盘的输入,每个电梯也写为一个线程;托盘模拟了容纳未处理的请求的容器,供输入线程与电梯线程交互使用。托盘的所有方法全部加锁以防止出现线程安全错误。
由于我在大部分情况下都采用了自由竞争策略,因此不需要显示的设置调度器,而是将请求的分解和分配分别放在输入和电梯线程中,而托盘只设置 addRequest, query, get
等操作。
这次作业较为简单,输入只涉及提供请求,电梯从楼座中选择请求接受,楼座(即托盘)将请求分类存储。
UML类图:
时序图:
后两次作业UML时序图与之相似,不再额外画出。
这次作业新增了横向电梯,但是其实没有什么本质改变,我采用了建立电梯抽象类的方法,仅将少部分横向、纵向电梯不同的方法设置为抽象方法,大部分电梯开关门、上下行、乘客进出对于两种电梯没有区别,可以在抽象电梯类中实现。同时将楼座和楼层抽象为容器(Container)和站点(Station)。对于加入新电梯的请求,只要新开一个对应线程即可。
UML类图:
首先自定义的要求非常容易处理,只要将那些部分从常量变成变量或方法即可。
难点在于请求的分解,我采用了静态分解的方式,也就是在请求被发起时就将其分解为若干横向和纵向请求,因为具体的数据分布无法得知,因此采用静态分配能较大幅度减少编码复杂度。
UML类图:
对第七次作业进行基于度量的分析(只保留部分):
method
CogC
ev(G)
iv(G)
v(G)
code.Elevator.run()
25.0
7.0
10.0
16.0
code.CompoundRequest.solve()
19.0
1.0
12.0
12.0
code.FloorElevator.getNextState(int)
16.0
7.0
7.0
12.0
code.Elevator.checkEnter()
13.0
4.0
6.0
8.0
code.BuildingElevator.getNextState(int)
12.0
9.0
5.0
9.0
code.Elevator.checkExit()
7.0
4.0
4.0
6.0
code.Container.connect(char, char)
6.0
4.0
4.0
6.0
code.PassengerRequester.run()
6.0
3.0
5.0
5.0
code.DirectRequest.DirectRequest(int, int, char, char, Container)
5.0
2.0
2.0
5.0
code.PassengerRequester.addRequest(Request)
5.0
1.0
3.0
3.0
code.Elevator.doorOpen()
4.0
3.0
3.0
5.0
code.Elevator.moveDown()
4.0
2.0
4.0
5.0
code.Elevator.moveUp()
4.0
2.0
4.0
5.0
code.Elevator.passengerExit(CompoundRequest)
4.0
2.0
4.0
5.0
code.Station.getDown(Elevator)
4.0
3.0
4.0
4.0
code.Station.getUp(Elevator)
4.0
3.0
4.0
4.0
code.Station.queryDown(Elevator)
4.0
3.0
3.0
4.0
code.Station.queryUp(Elevator)
4.0
3.0
3.0
4.0
code.Elevator.elevatorEmpty()
3.0
3.0
2.0
3.0
code.Elevator.passengerEnter(CompoundRequest)
3.0
2.0
3.0
4.0
code.Main.main(String[])
3.0
1.0
4.0
4.0
code.PassengerRequester.addCompoundRequest(CompoundRequest)
3.0
2.0
2.0
3.0
code.CompoundRequest.getFirstRequest()
2.0
2.0
2.0
2.0
code.CompoundRequest.tim(int)
2.0
1.0
1.0
3.0
code.Container.waitForNext()
2.0
1.0
3.0
3.0
code.DirectRequest.buildingName()
2.0
2.0
2.0
3.0
code.DirectRequest.floorName()
2.0
2.0
2.0
3.0
code.DirectRequest.getFromStation()
2.0
2.0
2.0
2.0
code.DirectRequest.getToStation()
2.0
2.0
2.0
2.0
code.DirectRequest.up()
2.0
2.0
1.0
2.0
code.Elevator.doorClose()
2.0
2.0
2.0
3.0
code.Station.addRequest(CompoundRequest, boolean)
2.0
1.0
2.0
2.0
Total
187.0
142.0
176.0
220.0
Average
2.174418
1.651162
2.046511
2.558139
其中复杂度最高的几个方法 Elevator.run
,Elevator.checkEnter()
,Elevator.checkExit()
是电梯运行的核心部分, CompoundRequest.solve()
, FloorElevator.getNextState(int)
, BuildingElevator.getNextState(int)
是电梯调度的核心部分,这两部分决定了大部分电梯运行的主要逻辑,也是我编写代码主要思考的部分。
总的来讲,这些复杂度数值还在可以接受的范围内。
如果是纵向请求,则直接加入对应容器,否则选择总距离最短、换乘次数最少、接受任务最少的横向电梯作为中转,代码如下:
public void solve() {
steps.clear();
if (this.fromBuilding == this.toBuilding) {
Container container = containers.get(fromBuilding);
steps.add(new DirectRequest(fromFloor, toFloor, fromBuilding, toBuilding, container));
container.addUnfinishedRequest();
} else {
int dis = Integer.MAX_VALUE;
int tim = Integer.MAX_VALUE;
int unf = Integer.MAX_VALUE;
int floor = 1;
for (int i = 1; i <= Main.getFloorCount(); ++i) {
Container container = containers.get(Main.floorIdToName(i));
if (container.connect(fromBuilding, toBuilding)) {
if (dis(i) < dis) {
floor = i;
dis = dis(i);
tim = tim(i);
unf = container.getUnfinishedRequest();
} else if (dis(i) == dis && tim(i) < tim) {
floor = i;
tim = tim(i);
unf = container.getUnfinishedRequest();
} else if (dis(i) == dis && tim(i) == tim && container.getUnfinishedRequest() < unf) {
floor = i;
unf = container.getUnfinishedRequest();
}
}
}
if (this.fromFloor != floor) {
Container container1 = containers.get(fromBuilding);
steps.add(new DirectRequest(fromFloor, floor, fromBuilding, fromBuilding, container1));
container1.addUnfinishedRequest();
}
Container container2 = containers.get(Main.floorIdToName(floor));
steps.add(new DirectRequest(floor, floor, fromBuilding, toBuilding, container2));
container2.addUnfinishedRequest();
if (floor != this.toFloor) {
Container container3 = containers.get(toBuilding);
steps.add(new DirectRequest(floor, toFloor, toBuilding, toBuilding, container3));
container3.addUnfinishedRequest();
}
}
}
线程的结束可以理解为特殊的请求,即输入线程请求结束整个程序,然后由容器(托盘)向电梯发出停止的请求,电梯在执行完所有请求后结束自身。
PassengerRequester:
for (Character key : containers.keySet()) {
containers.get(key).requestTerminate();
}
Container:
private void checkStop() {
if (stop()) {
notifyAll();
}
}
public synchronized boolean stop() {
return requestTerminate && unfinishedRequest == 0;
}
public synchronized void requestTerminate() {
requestTerminate = true;
checkStop();
}
Elevator:
private boolean elevatorEmpty() {
for (Queue<CompoundRequest> q : this.acceptedRequests) {
if (!q.isEmpty()) {
return false;
}
}
return true;
}
private boolean checkStop() {
return container.stop() && elevatorEmpty();
}
采用 wait-notify 机制,在 Container 里进行通信:
public synchronized void addRequest(CompoundRequest compoundRequest) {
stations.get(compoundRequest.firstRequestFromStation())
.addRequest(compoundRequest,compoundRequest.firstRequestDirection());
notifyAll();
}
public synchronized void waitForNext() {
try {
if (!stop()) {
wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
对于每个楼层、楼座,将所有请求按每层上下分到各个队列里。电梯使用 getNextState
来接收新请求。对于纵向电梯,优先选取在其上方向上的请求,对于横向电梯,优先选取离其最近的请求。
我采用了将所有请求视为同一方向的策略。
我采用了自由竞争的方式,即各个电梯同时去找各个请求,先到先得,这样在简化代码难度的情况下也能保证不错的性能。
\[bug惩罚分=未被修复的bug数\times\alpha+非合并修复对应的bug数+合并修复次数,\quad其中\alpha=2
\]
我采用了随机生成数据与手动测试相结合的方式,通过随机大量数据来覆盖大部分错误,同时手动针对电梯超载、在错误楼层停下等bug进行测试。
我使用了 checkEnter
函数判断了是否有乘客进入,接下来错误的认为只有电梯移动和空等的可能,但是实际上可能在 checkEnter
后出现请求,使得我的电梯错误运行。
手机扫一扫
移动阅读更方便
你可能感兴趣的文章