301
社区成员
发帖
与我相关
我的任务
分享

本次作业主要关注三个问题:
输入线程InputThread获取用户请求,为生产者;电梯线程Elevator处理用户请求,为消费者。请求队列RequestQueue用于存放请求,本人为每个电梯实例化一个专属的请求队列,用于存放即将乘坐电梯的乘客。
本次作业中的请求内容包括乘客编号、起始楼层和目的楼层,主要内容如下:
public class Person {
private final int id;
private final int fromFloor;
private final int toFloor;
public Person(int id, int fromFloor, int toFloor) {
this.id = id;
this.fromFloor = fromFloor;
this.toFloor = toFloor;
}
}
每个请求在输入线程中,会根据指定的电梯存放到对应的请求队列中。
请求队列的设计参考上机作业,主要内容如下:
public class RequestQueue {
private final ArrayList<Person> people;
private boolean isEnd;
public synchronized Person getOnePerson(int floor, int direction) {
if ((!isEnd) && people.isEmpty()) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (people.isEmpty()) {
return null;
} else {
for (Person person : people) {
if (person.getFromFloor() == floor && (person.getDirection() * direction > 0)) {
//同层同方向
people.remove(person);
return person;
}
}
return null;
//已经get了本层所有能上电梯的人
}
}
}
当消费者从请求队列中获取请求时,只获取同层且目标方向与电梯运行方向相同的请求。
请求队列会同时被输入线程和电梯线程访问,因此需要加锁避免线程安全问题。本人对RequestQueue类中,所有涉及成员变量读写的方法都加了synchronize锁:
public synchronized void addPerson(Person person);
public synchronized Person getOnePerson(int floor, int direction);
public synchronized void waitPerson();
public synchronized void setEnd(boolean isEnd);
public synchronized boolean isEnd();
public synchronized boolean isEmpty();
public synchronized int getSize();
public synchronized ArrayList<Person> getPeople();
本单元的电梯运行均采用Look策略,具体内容如下:
每台电梯初始的运行方向均规定为向上。
当电梯运行到某一层时,首先判断是否需要开门:
如果不需要开门,则判断电梯里是否有人:
请求队列为空时,如果输入结束,说明之后不会再出现乘客,电梯运行结束;如果输入还未结束,则等待之后可能出现的乘客。请求队列非空时,判断电梯运行方向上是否有发出请求的乘客,如果有则电梯按原运行方向继续运行,没有则电梯反向。
需要注意的有两点:
本次作业的调度策略通过Strategy类中的getAdvice方法实现,该方法返回一个枚举类Advide,作为电梯下一步的运行策略。
public synchronized Advice getAdvice(
int personNum, int maxNum, int posFloor, int direction,
RequestQueue requestQueue, HashSet<Person> peopleInEle);
由上述调度策略可知,电梯的运行模式主要有OVER, MOVE, REVERSE, OPEN, WAIT5种,电梯根据getAdvice方法的返回值做出反应:
public synchronized void run() {
while (true) {
Advice advice = strategy.getAdvice(id,
personNum, posFloor, direction, requestQueue, people);
if (advice == Advice.OVER) {
break;
} else if (advice == Advice.MOVE) {
move();
} else if (advice == Advice.REVERSE) {
direction = direction * -1;
} else if (advice == Advice.WAIT) {
requestQueue.waitPerson();
} else if (advice == Advice.OPEN) {
open();
if (requestQueue.getSize() > 0 && maintain != 1) {
Person person = null;
if (personNum < maxNum) {
person = requestQueue.getOnePerson(posFloor, direction);
}
while (person != null && personNum < maxNum) {
in(person);
if (personNum < maxNum) {
person = requestQueue.getOnePerson(posFloor, direction);
}
}
}
out();
close();
}
}
}


本次作业主要关注三个问题:
本人最初选取的策略为所有电梯自由竞争,先到的电梯接取乘客。但由于作业要求每个乘客分配一个指定的电梯,并在电梯移动前输出Receive信息,因此本人在改进Receive输出的过程中遇到了较大困难,最终选择重构。
重构后对于每个请求均匀分配电梯,按电梯编号顺序轮流将请求放入对应电梯的请求序列,具体内容如下:
if (request instanceof PersonRequest) {
//......
requestQueues.get(order).addPerson(person, order);
order += 1;
if (order > 6) {
order -= 6;
}
}
本人为电梯线程添加了maintain成员,表示电梯是否接收到Reset请求。当输入线程接收到Reset请求时,会将电梯的maintain置为1:
if (request instanceof ResetRequest) {
//......
elevators.get(eleId).setMaintain(1, resetRequest.
getSpeed(), resetRequest.getCapacity());
requestQueues.get(eleId).reset();
}
电梯每次请求运行策略时,会先判断maintain是否为1,即当前是否需要Reset:
public synchronized void run() {
while (true) {
if (maintain == 1) {
if (personNum > 0) {
open();
outForMaintain();
close();
}
TimableOutput.println("RESET_BEGIN-" + id);
reset(resetTime, resetNum);
TimableOutput.println("RESET_END-" + id);
requestQueue.resetEnd(id);
maintain = 0;
continue;
}
//......
}
}
电梯Reset之前会先放出电梯内的所有乘客。如果乘客的目的楼层为电梯当前所在楼层,则直接将乘客放出;如果不是乘客的目的楼层,则放出的乘客作为新的请求重新加入该电梯对应的请求队列中:
public void outForMaintain() {
while (personNum > 0) {
for (Person person : people) {
if (person.getToFloor() == posFloor) {
//......
} else {
//......
Person newPerson = new Person(person.getId(),
posFloor, person.getToFloor());
requestQueue.addPerson(newPerson, id);
}
}
}
}
将乘客加入请求队列时输出Receive信息即可。
根据要求,Reset-Begin输出后到Reset-End输出前,电梯都不可以输出Receive信息,且之前所有未完成的Receive请求均失效。因此在电梯Reset期间分配到的乘客都暂时不输出Receive信息:
public synchronized void addPerson(Person person, int order) {
if (!isReset) { //elevator not in reset
TimableOutput.println("RECEIVE-" + person.getId() + "-" + order);
}
people.add(person);
notifyAll();
}
由于每个电梯都分配了对应的请求队列,因此在请求队列中添加了isReset成员,表示电梯是否处于Reset状态。输入线程接收到Reset请求后调用以下方法:
public synchronized void reset() {
this.isReset = true;
notifyAll();
}
Reset结束后,电梯调用以下方法,重新Receive未到达目的地的乘客:
public synchronized void resetEnd(int id) {
for (Person person : people) {
TimableOutput.println("RECEIVE-" + person.getId() + "-" + id);
}
this.isReset = false;
}


本次作业主要关注三个问题:
Reset为双轿厢的重置过程与上一次作业类似,重置后产生两个新的电梯线程,原本的电梯线程结束运行。本人为了避免过多逻辑判断,为A类和B类双轿厢电梯分别设置了DoubleElevatorA类、DoubleElevatorB类和对应的策略类DoubleStrategyA、DoubleStrategyB。双轿厢电梯的两个轿厢共用原本的请求队列。
对于两类电梯可以接到的乘客(包括输入线程提供的和需要换乘的两类)约束如下:
A类电梯接到,运行方向向上的只能由B类电梯接到;A类电梯接到;B类电梯接到。因此对于Receive输出更改如下:
public synchronized void addPerson(Person person, int order) {
if (!isDouble) {
if (!isReset) {
//......
}
} else {
if (person.getFromFloor() == transforFloor) {
if (person.getDirection() > 0) {
//...
} else {
//...
}
} else if (person.getFromFloor() < transforFloor) {
//...
} else {
//...
}
people.add(person);
}
notifyAll();
}
双轿厢电梯的运行策略也有所更改:
A类电梯只能接到从换乘楼层以下发出请求的乘客和从换乘楼层下楼的乘客。如果队列中没有可以接到的乘客,则电梯在原位置等待新乘客的加入。对于另一轿厢中是否还有没到达目的地的乘客的判断,本人在RequestQueue中添加了对两轿厢负责乘客数量的统计成员numA、numB。在addPerson分配时增加对应的num,并在换乘时和到达目的地后对该变量进行维护。为了避免线程安全问题,对该变量的读写也都需要加synchronized锁。
当双轿厢电梯到达换乘楼层后,需要先将所有乘客放出。与Reset类似,要判断放出的乘客目的地是否为换乘楼层,如果不是则需要重新放至请求队列中:
public void outForTransfor() {
while (personNum > 0) {
for (Person person : people) {
if (person.getToFloor() == posFloor) {
//......
requestQueue.delAPerson(); //此处以A为例
} else {
//......
Person newPerson = new Person(person.getId(),
posFloor, person.getToFloor());
requestQueue.addPerson(newPerson, id);
requestQueue.delAPerson(); //此处以A为例
}
}
}
}
由于前文的接取策略,新放入请求队列的乘客一定会被另一轿厢接取。
本人对于电梯对换乘楼层的访问加锁,进入换乘楼层前请求锁,离开换乘楼层后释放。该过程也在电梯对应的请求队列中实现:
public synchronized void transFor() {
while (isTransfor) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
isTransfor = true;
notifyAll();
}
public synchronized void transforEnd() {
isTransfor = false;
notifyAll();
}
当到达换乘楼层的电梯完成乘客的in和out等请求后,会按可运行方向运行一层离开换乘楼层,并释放换乘锁。具体内容如下:
if (advice == Advice.MOVE) {
if ((posFloor + direction) != transforFloor) {
int lastFloor = posFloor;
move();
if (lastFloor == transforFloor) {
requestQueue.transforEnd();
}
} else {
requestQueue.transFor();
move();
}
}
//......
if (advice == Advice.OPEN) {
//......
if (posFloor == transforFloor) {
if ((posFloor + direction) > transforFloor) {
direction = direction * -1;
}
move();
requestQueue.transforEnd();
}
}
getAdvice中添加参数即可解决。Receive输出时出现了问题:电梯接收到Reset请求后便不输出Receive信息,但仍将新分配到的乘客加入请求队列。此时如果电梯还处于上一次开门之后、乘客进入之前的状态,会把尚未Receive但已加入请求队列的乘客get进电梯。修改方式为在乘客进入电梯前添加对于是否接收到Reset请求的判断,若已经接收到Reset请求,则不允许乘客再进入电梯。通过本单元的学习,我对多线程的实现方式有了初步了解。在完成作业的过程中体会到线程安全的重要性,并对同步块和锁的概念有了进一步认识。