302
社区成员
由于我架构的设计,整个代码中存在三种队列:waitingQueue
、RequestQueue
和LeaveQueue
,这三个队列就是整个架构中最核心的“临界资源”。
具体体现为,waitingQueue
链接了Input
和Scheduler
,而RequestQueue
链接了Scheduler
和Elevator
,每个队列都处在两层结构之间的“临界区域”。
这种架构最优秀的地方,在于同步块会很集中,也就不容易遗漏。
每次访问各种Queue
时,仅需要对Queue
上锁,并且为队列中的每个方法上锁,即可确保各种类之间不会出现数据竞争。
也正是这种清晰的代码结构,给我的代码带来了很强的生命力,极其容易阅读、理解和维护。
调度器通过waitingQueue
链接着Input
,通过RequestQueue
链接着Elevator
,是整个代码的枢纽所在。正如前文所述,他与其他类的交互,正是通过waitingQueue
、RequestQueue
这两个线程安全的类实现的,也就不存在任何的线程安全问题。
public class Schedule extends Thread {
private WaitingQueue waitingQueue;
private ArrayList<RequestQueue> processingQueues;
private ArrayList<Elevator> elevators;
Input
中的数据会通过waitingQueue
的addRequest
方法移到调度器中,再通过各种线程安全方法通过队列转移到Elevator
,实现了工程流水线的封装。
调度策略:整体的调度策略是以时间为主要核心的。通过影子电梯的模拟方式,提前计算出每个电梯需要花费的时间,再对每个请求进行分配。
纵观整个单元,调度器是最稳定的一个类之一,仅次于输入流。在第一次迭代时,仅仅只是更改了调度策略,从固定调度改为计算时间后分配。修改的代码量仅有数行(计算过程被封装在电梯的函数中);而第二次迭代则完全没有任何变化。
最主要的一点在于对换乘楼层的互斥访问,通过设立一个mutex
信号量,保证每次仅有一个电梯访问换乘区,另一个电梯必须等待信号量释放。
public synchronized void moveToNextFloor() {
int nextFloor = curFloor + direction;
int oldFloor = curFloor;
if (type != 0 && nextFloor == trans) {
if (isTrans[id] == 0) {
isTrans[id] = 1;
} else {
while (isTrans[id] == 1) {
try {
this.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
this.curFloor = nextFloor;
sleepForATime((long)(moveTime * 1000));
output("ARRIVE", 0);
if (type != 0 && oldFloor == trans) {
if (isTrans[id] == 1) {
isTrans[id] = 0;
synchronized (otherElevator) {
otherElevator.notify();
}
}
}
}
其次保证不相碰的一点就是让电梯及时让位。通过让电梯在空闲时移出临界区,来为移动的电梯腾开换乘区的位置
if (inQueue.isEmpty()) {
synchronized (processingQueue) {
if (processingQueue.isEmpty() && type != 0 && curFloor == trans) {
setDirection();
moveToNextFloor();
}
最明显的bug莫过于与程序结束相关的bug。在第一次作业中,我们注意到waitingQueue
仅仅来源于输入流,当输入结束时,会引起等待队列--处理中队列一系列的结束,最终让程序结束。
但是这跟第二次作业时不同的。在第二次作业中,waitingQueue
还有一种来源——reset
释放后的乘客。如果不加以处理,就会导致程序结束时还有人没有送完的情况。
而第三次作业又多了一条路径,也就是换乘的乘客。
这三条路径需要对应三种不同的end
信号。实现这个end
信号的过程需要一点技巧,也算是这个单元很有趣的bug修复过程。
这个单元的debug
极其折磨。由于无法使用断点等办法,只能使用手动输出的方式获取程序运行的路径;但是程序的运行又极其的慢,修改一次输出信息,就要重新跑数十秒。目前只有通过一些黑科技来加快“时间流速”,其他的好办法一概不知,希望uu来点建议。
线程一旦出现问题,不管是debug的成本,还是发现问题后修复的成本都极其高(而且对心态打击极大),所以每次写代码时必须谨慎(不然一旦出现线程安全问题就容易跟我一样开摆)
而这次我的作业极其符合层次化设计,四层大类+三条线程安全连接的数据结构,让我的代码极其清晰而易于维护,我将学习这次的经验,并且将这次做的好的地方继续发扬下去。