301
社区成员
发帖
与我相关
我的任务
分享BUAA OO Unit2总结
第二单元学习的主题是多线程,体会线程安全和层次化设计的思想。三次作业逐步迭代,从最初的指定乘客分配到后来的自己设计分配策略再到电梯的两类reset,尽管在过程中被架构设计,诸多bug和更加全面的数据构造所困扰,甚至废寝忘食,但回过头来,三周的训练仍然让我受益良多,尤其是在层次化架构和多线程的设计上进步显著。
由于三次作业底层逻辑相似,因而并没有进行大规模的重构,作业整体架构类似,只有细节上的修改,具体分析如下:
第一次作业的具体要求是:模拟多线程实时电梯系统,模拟的电梯系统是一个类似北京航空航天大学新主楼的电梯系统,楼座内有多部电梯,电梯可以在楼座内1-11层之间运行。系统从标准输入中读入乘客请求信息(起点层,终点楼层),请求调度器会根据此时电梯运行情况(电梯所在楼层,运行方向等)将乘客请求合理分配给某部电梯,然后被分配请求的电梯会经过上下行,开关门,乘客进入/离开电梯等动作将乘客从起点层运送到终点层。


代码整体可以分为九个线程:
for (int i = 0; i < 6; i++) {
RequestQueue parallelQueue = new RequestQueue();
processingQueues.add(parallelQueue);
Process process = new Process(parallelQueue, i + 1);
process.start();
}
Request request = processingQueue.getOneRequestAndRemove();
if (request == null) {
continue;
}
int a = request.getFromFloor();
int b = request.getToFloor();
int c;
if (a == processingQueue.getWeiZhi()) {
c = (b > a) ? 1 : -1;
} else if (a < processingQueue.getWeiZhi()) {
c = -1;
} else {
c = 1;
}
processingQueue.setFangXiang(c);
processingQueue.addNewrequest(request);
processingQueue.setZhuangTai(5);
if (processingQueue.getNowrequests().isEmpty()) {
processingQueue.setZhuangTai(1);
continue;
}
int a = processingQueue.getWeiZhi();
int b = processingQueue.getFangXiang();
try {
Thread.sleep(400);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
processingQueue.setWeiZhi(a + b);
TimableOutput.println("ARRIVE-" + processingQueue.getWeiZhi() + "-" + type);
processingQueue.setZhuangTai(5);


可以看到代码的整体结构与第一次作业类似,完全没有新增类,只是依据三处改动对代码细节作出改动:
这次作业需要自己设计分配策略,我在考虑实际情况后决定选择以尽量降低电梯总运行时间为目的的平均分配方法,但是也可能会出现电梯耗电量较高的问题。
具体来说,在每分配一个请求前:
int a = processingQueues.get(0).getNowrequests().size() +
processingQueues.get(0).getRequests1().size();
for (int i = 1; i < 6; i++) {
int b = processingQueues.get(i).getNowrequests().size() +
processingQueues.get(i).getRequests1().size();
if (b < a) {
a = b;
x = i;
}
}
int n = getNb(request);
processingQueues.get(n).askReset();
TimableOutput.println("RECEIVE-" + request.getPersonId() + "-" + (n + 1));
processingQueues.get(n).addRequest(request);
这里的**askReset()**方法位于共享类中,作用是如果当前电梯在reset则wait();在reset结束后会将其唤醒。为了保证电梯重置信号传达的及时性,选择在InputThread线程直接将电梯的reset信号置位,并传递必要的reset信息。
else if (request instanceof ResetRequest) {
Request1 request1 = new Request1(0,0,0,
((ResetRequest) request).getElevatorId());
request1.setIfPerson(0);
request1.setCapacity(((ResetRequest) request).getCapacity());
request1.setSpeed(((ResetRequest) request).getSpeed());
processingQueues.get(request1.getElevatorId() - 1).setCa(
request1.getCapacity());
processingQueues.get(request1.getElevatorId() - 1).setSp(
request1.getSpeed());
processingQueues.get(request1.getElevatorId() - 1).setIfReset(1);
}
在reset具体处理的过程中,采取的方法是将reset视为电梯运行的一个状态,设为状态六。可能从状态一(第一个请求就是reset)或状态四跳转到状态六,这里需要注意的一点是还需要将未处理完的请求添加回waitQueue,状态六结束后需要跳转回状态一。
private void reset() {
TimableOutput.println("RESET_BEGIN-" + type);
processingQueue.setCapacity(processingQueue.getCa());
processingQueue.setSpeed(processingQueue.getSp());
Iterator<Request1> iterator = processingQueue.getRequests1().iterator();
while (iterator.hasNext()) {
Request1 it = iterator.next();
Request1 request1 = new Request1(it.getFromFloor(), it.getToFloor(),
it.getPersonId(),0);
waitQueue.addRequest(request1);
iterator.remove();
}
try {
Thread.sleep(1200);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
TimableOutput.println("RESET_END-" + type);
}
与作业一相同,依旧是在RequestQueue这个共享类中为可能在两个线程中对同一属性进行访问的方法加锁,来保证线程安全。


可以看到代码的整体结构仍然与第一次作业类似,新增了一个Flag共享类,用来避免两个轿厢碰撞。
第二类电梯实质上是将一个电梯分为两个电梯,只是在一定程度上限定了分裂后电梯的运行范围。在具体实现上有以下几个重点:
private void build() {
RequestQueue parallelQueue = processingQueues.get(type + 5);
parallelQueue.setSign(1);
parallelQueue.setCapacity(processingQueue.getCa());
parallelQueue.setSpeed(processingQueue.getSp());
parallelQueue.setTransferFloor(processingQueue.getTransferFloor());
parallelQueue.setWeiZhi(parallelQueue.getTransferFloor() + 1);
parallelQueue.setFind(type);
processingQueue.setWeiZhi(processingQueue.getTransferFloor() - 1);
processingQueue.setSign(1);
Flag flag = new Flag();
processingQueue.setOccupied(flag);
parallelQueue.setOccupied(flag);
Process process = new Process(parallelQueue, type + 6, waitQueue, processingQueues);
process.start();
}
private boolean checkIfEnd() {
if (processingQueue.isEnd() && processingQueue.isEmpty() &&
processingQueue.isEmpty1()) {
num = num + 1;
}
return num == 2;
}
public synchronized void setOccupied() {
waitRelease();
state = State.OCCUPIED;
notifyAll();
}
private synchronized void waitRelease() {
notifyAll();
while (state == State.OCCUPIED) {
try {
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public synchronized void setRelease() {
this.state = State.UNOCCUPIED;
notifyAll();
}
这样就可以保证不会出现电梯碰撞的情况。第二单元关于多线程的学习对于我们的层次化设计提出了更高的要求,因为我们需要同时保证线程的安全性,一方面层次化设计可以避免过多的bug产生,另一方面也有助于我们通过阅读代码逻辑来debug(因为多线程中的bug难以复现,很多时候不得不反复分析代码逻辑)。同时也让我开始重视书写注释(因为之前基本是通过调试来debug,因此在很长一段时间忽视了精确简短的注释),来便于自己回过头来阅读代码逻辑。
第二单元的学习相比于第一单元给我带来了很大的压力,主要是不熟悉线程的创建、运行等基本操作,不熟悉多线程程序的设计方法,也很难确定哪些方法需要加锁。但是在这一过程我也真正开始接触具有极高实用价值的多线程架构,与此同时OS课程也正在讲关于进程和线程的知识,通过对理论的学习和在OO课程中的实践,让我受益匪浅。最后,还是希望我的头发不要掉得太快。