301
社区成员
发帖
与我相关
我的任务
分享第五次作业中,临界资源只有各个电梯的请求列表 RequestTable 类,因此对其的读写操作都在 synchronized 块中。
同时,为了防止电梯线程轮询,当电梯等待请求输入时,会访问该类中的 waitForQueue() 方法,该方法同样由 synchronized 修饰:
if (isEmpty() && !end) {
if (isEmpty() && !end) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
notifyAll();
}
第六次作业中,临界资源有总请求列表 WaitList 类、单个电梯的请求列表 PersonTable 类,对这两类的读写操作都在 synchronized 中。
同时还加入了 RequestCounter 类,用于辅助结束线程,该类由电梯线程和输入线程访问,其中的速写均要在同步块中实现:
public synchronized void acquire() {
// ...
}
public synchronized void complete() {
// ...
}
第七次作业新增 ElevatorMap 类,用于记录12个电梯进程的状态,判断该进程是否起用。对这类的读写操作都在 synchronized 中。调度线程对其进行读操作,电梯线程对其进行写操作。
还加入了 TransFloor 类,用于记录交换楼层是否被占据,由两个电梯进程进行读写。对其的读写操作同样需要在 synchronized 中实现。
第五次作业中,不存在多电梯协同调度。直接由输入线程将请求加入各个电梯的请求列表。
requestTableHashMap.get(request.getElevatorId()).addRequest(request);
UML图 如下:

调度策略上仅存在单步电梯的调度策略,这里使用了 Look 算法。我们日常的电梯也大多使用这种算法。
Look算法:
第六次作业不再指定电梯,且新增 RESET 请求,并要求输出 RECEIVE。
UML类图:

这次作业相对于第五次而言难上许多,首先是不在指定电梯,这就要我们建立一个新的调度器将请求分配给各个电梯;然后是添加了 RESET 请求,这个请求相当于添加了换乘请求;最后是添加的 RECEIVE 请求,将调度算法中最简单的自由竞争禁掉了。
在这次调度策略中,我选择了随机数调度,这种调度算法较为简单,容易实现,且上线限较高。缺点就是下限太低了。
private int whichToReceive(Person p) {
Random random = new Random();
ArrayList<Integer> elevatorIds = new ArrayList<>();
getIds(elevatorIds, p);
while (elevatorIds.isEmpty()) {
try {
Thread.sleep(1200);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
getIds(elevatorIds, p);
}
return elevatorIds.get(random.nextInt(elevatorIds.size()));
}
getIds() 方法判断那些电梯能够接受请求,如正在 RESET 的电梯不能 RECEIVE。
第七次作业调度算法与第六次一样采用了随机调度法。因此构架类似。

private int whichToReceive(Person p) {
Random random = new Random();
ArrayList<Integer> elevatorIds = new ArrayList<>();
getIds(elevatorIds, p);
while (elevatorIds.size() <= 1) {
try {
Thread.sleep(1200);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
getIds(elevatorIds, p);
}
return elevatorIds.get(random.nextInt(elevatorIds.size()));
}
由于第六次的问题,这里在五个电梯重制时,再等1.2秒,以防大量请求被分配给一部电梯,导致超时。
这里在双轿厢的电梯井中,两部电梯线程有一个共享类 TransFloor。
public synchronized void setExFlag(ExFlag exFlag) {
if (exFlag == ExFlag.BUSY) {
waitForFree();
}
this.exFlag = exFlag;
notify();
}
private synchronized void waitForFree() {
notify();
while (exFlag == ExFlag.BUSY) {
try {
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
电梯移动时检测一下,下一步是否是到交换层。
if (elevator.isDoubleCar() && curFloor == elevator.getExFloor()) {
exFlag.setExFlag(ExFloor.ExFlag.BUSY);
}
TimableOutput.println("ARRIVE-" + curFloor + "-" + elevator.getString());
if (elevator.isDoubleCar() && curFloor - direction == elevator.getExFloor()) {
exFlag.setExFlag(ExFloor.ExFlag.FREE);
}
bug:轮询:电梯在等待时,电梯线程轮询,占据 CPU 时间;
处理方式:在电梯等待时,使用 RequestTable 类中的 waitForQueue() 方法。
死锁:由于线程不安全导致各个线程进入 waiting 状态,无法进一步进行;
处理方式:
RESET 状态的电梯调度做的不好,导致超时。
这一部分主要是没处理好对于调度的处理,在五个电梯重制时,再等1.2秒,以防大量请求被分配给一部电梯。
我这里使用了Java自带的一个工具 Visual VM,他可以监控各个线程的运行状态。
线程状态

CPU时间

三次架构中,第一次到第二次的架构变化更大些,而第二次到第三次的变化较小。
而第一次迭代主要是加入了调度器线程。
三次作业中的稳定内容是电梯类的各种属性。
易变内容包括输入线程中的结束方式,调度器的调度策略实现。
Visual VM 帮Debug解决了不少问题。