301
社区成员
发帖
与我相关
我的任务
分享
线程安全是线程安全类的属性,线程同步包含于具有线程同步功能的方法语义所自然说明的。线程安全和同步都应当被封装在类内部完成,以实现对使用者的透明化。

对于稳定和变化,应当有两层次的理解:
首先,开闭原则在宏观设计上的语义告诉我们,已完成的设计都应当是稳定的部分,变化的部分永远是通过增加而不是修改原有设计而完成的。所以我们进行每一部分设计的时候,都应当试图使其成为将来设计的稳定部分,能够进行扩展和兼容。
其次,良好的面向对象的层次设计,应当在每个抽象层次处理自己的逻辑,这一原则要求我们对于新增的变化的部分要进行封装,也即“面向接口(抽象层次)编程,而不要面向具体实现编程。”这要求我们仔细设计和分化“变化”,将变化的部分根据其抽象层次进行分化,使处于同一个抽象层次的变化内容被抽象为一个接口(或其他抽象层次的表述)。
首次迭代
自主调度功能通过两个添加实现:调度方法schedule()和其使用的调度优先级计算函数schedulePriority()。服务线程Server增加Buffer用于配合调度方法以及之后的请求重分配。
电梯重置功能实际上是增加了电梯的一种功能,即增加了电梯的一种状态,以及与这个状态相关的各种路径。因此增加了RESET状态和motionReset()动作,电梯RESET返回的请求可以重用Server Buffer。
第二次迭代
双轿厢电梯设计时尽量融入先前的设计,需要调度器和电梯线程配合。首先引入电梯服务区间概念,调度器优先调度符合服务区间的电梯,并使用Best Fit策略;对于没有电梯能够完成的请求,拆分为两个请求并且先后调度。电梯线程仍然只需负责自己的请求,然而同一电梯井内需要完成防碰撞配合,具体分析见下文。
在前述设计中,双轿厢电梯都尽量被分割成互不相关的独立的两个运行着的电梯,以最大程度自然地兼容先前的设计思路。然而,实现双轿厢避碰则必然涉及一个电梯阅读另一个电梯的状态,也即线程同步协作问题。
// in reset motion:
int resetTransFloor = command.getResetTransFloor();
if (resetTransFloor != 0) {
// CHECKME create para elevator and modify self
ParaLock lock = new ParaLock();
// create para
String paraEid = eid + "-B";
Elevator ev = new Elevator(paraEid, resetTransFloor,
elevator.getMaxFloor(), resetTransFloor, command);
ElevatorThread et = new ElevatorThread(server, ev, paraEid, elevator, lock, command);
server.addElevator(paraEid, ev, et);
et.start();
// modify self
eid += "-A";
elevator.setFloor(command.getResetTransFloor() - 1);
elevator.setEid(elevator.getEid() + "-A");
paraLock = lock;
para = ev;
elevator.setRange(elevator.getMinFloor(), resetTransFloor);
elevator.setTransFloor(resetTransFloor);
}
// in moving motion:
// if is one of DCE:
if (elevator.getTransFloor() != 0) {
assert (paraLock != null);
if (// if moving to transferring floor from neighbouring floor, conflicts possible
(elevator.isUpperDcElevator() && elevator.atFloor(elevator.getTransFloor() + 1)
&& elevator.nextDirection() == -1)
||
(!elevator.isUpperDcElevator() && elevator.atFloor(elevator.getTransFloor() - 1)
&& elevator.nextDirection() == 1)
) {
Debugger.dbgPrintln("self: conflict, checking para", "elevator thread");
// will wait on any para conflicts until solved:
// 1. idle(not moving with direction != STAY) at transFloor
// 2. moving towards transFloor from neighbouring floor(direction != STAY)
checkParaConflicts();
}
}
private void checkParaConflicts() throws InterruptedException {
assert (para != null);
// check partner elevator for any conflicts:
boolean conflict = para.atFloor(para.getTransFloor());
Debugger.dbgPrintln("cond1=" + conflict, "elevator thread");
conflict |= (para.atFloor(para.getTransFloor() + 1)
&& para.nextDirection() == -1);
Debugger.dbgPrintln("cond12=" + conflict, "elevator thread");
conflict |= (para.atFloor(para.getTransFloor() - 1)
&& para.nextDirection() == 1);
Debugger.dbgPrintln("cond123=" + conflict, "elevator thread");
while (conflict) {
// let para go first if conflict
Debugger.dbgPrintln("para: conflict!", "elevator thread");
elevator.setDirection(Elevator.Direction.STAY);
paraLock.waitOn();
Debugger.dbgPrintln("para: noted!", "elevator thread");
conflict = para.atFloor(para.getTransFloor());
Debugger.dbgPrintln("cond1=" + conflict, "elevator thread");
conflict |= (para.atFloor(para.getTransFloor() + 1)
&& para.nextDirection() == -1);
Debugger.dbgPrintln("cond12=" + conflict, "elevator thread");
conflict |= (para.atFloor(para.getTransFloor() - 1)
&& para.nextDirection() == 1);
Debugger.dbgPrintln("cond123=" + conflict, "elevator thread");
}
}
private void schedule(PersonRequest request) {
Elevator elevator = elevators.get(request.getElevatorId() - 1); // EID starts at 1, so -1
// This thread ensures that once a new request is acquired from the request
// queue, it's immediately written to the scheduled elevator's fr_table, so
// that an elevator can read its scheduled requests in real time.
elevator.addRequest(request); // write request in elevator's fr_table
}
private int schedulePriority(Elevator elevator, PersonRequest request) {
int priority = 0;
// highest: idle
if (elevator.getDirection() == Elevator.Direction.STAY) {
return priority;
}
// moving towards:
// 1st: not full
if (elevator.isFull()) { return 9999; }
// 2nd: shorter distance
priority += 100 * Math.abs(request.getFromFloor() - elevator.getFloor());
// 3nd: less load
priority += 10 * elevator.getLoad();
// 4th: random of [0,10)
priority += (int) (Math.random() * 10);
return priority;
}
private void schedule(Request inputRequest) {
if (inputRequest instanceof PersonRequest) {
PersonRequest request = (PersonRequest) inputRequest;
// look in (towards + idle) for an elevator of shortest distance
int minPriority = 100000;
int selectedElevatorId = 0; // from 1 to 6
for (Elevator elevator : elevators) {
// elevator under reset, not operable
if (elevator.getState() == Elevator.State.RESETTING) { continue; }
// if away from the request, jump it
// jump on equal because this leads to uncertainty, the req may not get picked up
boolean jump = elevator.getDirection() != Elevator.Direction.STAY &&
elevator.getFloor() == request.getFromFloor();
jump |= elevator.getDirection() != Elevator.Direction.STAY
&& elevator.getFloor() < request.getFromFloor()
&& elevator.nextDirection() == -1;
jump |= elevator.getDirection() != Elevator.Direction.STAY
&& elevator.getFloor() > request.getFromFloor()
&& elevator.nextDirection() == 1;
if (jump) { continue; }
// measure priority and record
// smaller value of priority means higher priority level!!!
int priority = schedulePriority(elevator, request);
// "<"(instead of <=) means if multiple found, take the 1st
if (priority < minPriority) {
minPriority = priority;
selectedElevatorId = elevators.indexOf(elevator) + 1;
}
}
if (minPriority != 100000) {
// elevator found, start scheduling
Elevator elevator = elevators.get(selectedElevatorId - 1);// EID starts at 1, so -1
Debugger.timePrintln(
String.format("RECEIVE-%d-%d", request.getPersonId(), selectedElevatorId)
);
// This thread ensures that once a new request is acquired from the request
// queue, it's immediately written to the scheduled elevator's fr_table, so
// that an elevator can read its scheduled requests in real time.
elevator.addRequest(request); // write request in elevator's fr_table
}
else { // if all elevator moving away from the req, store req in buffer
Debugger.dbgPrintln("@Scheduler{}: req jumped", "scheduler");
bufferThread.addRequest(request);
}
} else if (inputRequest instanceof ResetRequest) {
Debugger.dbgPrintln("@Scheduler{}: rst_req", "scheduler");
Elevator elevator = elevators.get(((ResetRequest) inputRequest).getElevatorId() - 1);
elevator.addRequest(inputRequest);
}
}
public void noteElevatorDirectionChange() {
bufferThread.setStart(true);
requestQueue.note();
}
在指定调度模式下,调度器并没有实际意义上的调度功能,因此这一迭代关于自主调度的设计主要分为以下方面:
不使用单例调度器的考虑
private int schedulePriority(Elevator elevator, PersonRequest request) {
int priority = 0;
// 5th: random of [0,10)
priority += (int) (Math.random() * 10);
// highest: idle
if (elevator.getDirection() == Elevator.Direction.STAY) {
return priority;
}
// moving towards:
// 1st: not full
if (elevator.isFull()) { return 9999; }
// 2nd: shorter distance
priority += 1000 * Math.abs(request.getFromFloor() - elevator.getFloor());
// 3nd: less load
priority += 100 * elevator.getLoad();
// CHECKME best fit serving range
// 4th:
priority += 10 * elevator.fitRange(request);
return priority;
}
private void schedule(Request inputRequest) {
if (inputRequest instanceof PersonRequest) {
PersonRequest request = (PersonRequest) inputRequest;
// look in (towards + idle) for an elevator of shortest distance
int minPriority = 100000;
String selectedElevatorId = "";
boolean allReset = true;
boolean splitRequest = true;
boolean allBackwards = true;
synchronized (elevators) {
for (String eid : elevators.keySet()) {
Elevator elevator = elevators.get(eid);
Debugger.dbgPrintln("schedule for " + eid, "scheduler");
// elevator under reset, not operable
if (elevator.getState() == Elevator.State.RESETTING) {
continue;
}
allReset = false;
// CHECKME check range of request and elevator serving floors
if (!elevator.containRange(request)) {
continue;
}
splitRequest = false;
// if away from the request, jump it
// jump on equal because this leads to uncertainty, the req may not
// get picked up
boolean jump = elevator.getDirection() != Elevator.Direction.STAY &&
elevator.getFloor() == request.getFromFloor();
jump |= elevator.getDirection() != Elevator.Direction.STAY
&& elevator.getFloor() < request.getFromFloor()
&& elevator.nextDirection() == -1;
jump |= elevator.getDirection() != Elevator.Direction.STAY
&& elevator.getFloor() > request.getFromFloor()
&& elevator.nextDirection() == 1;
if (jump) {
continue;
}
allBackwards = false;
// measure priority and record
// smaller value of priority means higher priority level!!!
int priority = schedulePriority(elevator, request);
Debugger.dbgPrintln(eid + ": priority=" + priority, "scheduler");
// "<"(instead of <=) means if multiple found, take the 1st
if (priority < minPriority) {
minPriority = priority;
selectedElevatorId = eid;
}
}
}
if (allReset) {
Debugger.dbgPrintln("@Scheduler{}: req jumped, all resetting");
bufferThread.addRequest(request);
} else if (splitRequest) {
// CHECKME if no elevator contains the range:
// TODO pick a floor and split the request
int transFloor = 0;
synchronized (elevators) {
for (Elevator elevator : elevators.values()) {
if (elevator.getState() != Elevator.State.RESETTING) {
transFloor = elevator.getTransFloor();
}
}
}
Debugger.dbgPrintln(
"@Thread{ServerThread}: picked trans_flr" + transFloor, "server"
);
PersonRequest nextRequest = new PersonRequest(
transFloor, request.getToFloor(), request.getPersonId());
requestQueue.addRequest(new ParaRequest(
request.getFromFloor(), transFloor, request.getPersonId(), nextRequest));
} else if (allBackwards) {
Debugger.dbgPrintln("@Scheduler{}: req jumped", "scheduler");
bufferThread.addRequest(request);
} else {
synchronized (elevators) {
// elevator found, start scheduling
// EID starts at 1, so -1
Elevator elevator = elevators.get(selectedElevatorId);
Debugger.timePrintln(
String.format(
"RECEIVE-%d-%s", request.getPersonId(), selectedElevatorId)
);
// This thread ensures that once a new request is acquired from the request
// queue, it's immediately written to the scheduled elevator's fr_table, so
// that an elevator can read its scheduled requests in real time.
elevator.addRequest(request); // write request in elevator's fr_table
}
}
// End of PersonRequest handling
} else if (inputRequest instanceof ResetRequest) {
String eid;
if (inputRequest instanceof NormalResetRequest) {
eid = Integer.toString(((NormalResetRequest) inputRequest).getElevatorId());
} else {
eid = Integer.toString(((DoubleCarResetRequest) inputRequest).getElevatorId());
}
synchronized (elevators) {
Debugger.dbgPrintln("@Scheduler{}: rst_req", "scheduler");
Elevator elevator = elevators.get(eid);
elevator.addRequest(inputRequest);
}
}
}
对于双轿厢电梯兼容,调度器主要需要以下功能:
public class Debugger {
private static final boolean debugOut = false; // debug output switch
private static final HashMap<String, Boolean> infoSwitch = new HashMap<>();
private static final String eid = ""; // selected debugging elevator id
// seperate debug info switch
static {
infoSwitch.put("scheduler", false);
infoSwitch.put("elevator", false);
infoSwitch.put("elevator thread", true);
infoSwitch.put("read requests", false);
infoSwitch.put("server", true);
infoSwitch.put("reset", false);
infoSwitch.put("commandlist", true);
infoSwitch.put("command", true);
infoSwitch.put("buffer thread", false);
}
private static final boolean output = true; // required output switch
public static void dbgPrintln(Object o) {
if (debugOut) {
System.out.println(o);
}
}
public static void dbgPrintln(Object o, String type) {
if (infoSwitch.get(type)) {
dbgPrintln(o);
}
}
public static void dbgPrintln(Object o, String type, String selId) {
if (!eid.isEmpty()) {
if (eid.equals(selId)) {
dbgPrintln(o, type);
}
} else {
dbgPrintln(o, type);
}
}
// required output wrapper method
public static void timePrintln(Object o) {
if (output) {
TimableOutput.println(o);
}
}
}
| INDEX | CONTENT | DESCRIPTION |
|---|---|---|
| 1 | floor index 不匹配 | 使用floor 而非 floor - 1来访问相关数组 |
| 2 | 未添加满载相关功能 | / |
| 3 | 多个运行策略算法实现逻辑错误 | / |
| 4 | 修复满载功能实现逻辑错误 | / |
| 5 | 修复服务线程和电梯线程结束条件不正确 | 新增了电梯请求回填后,其要被考虑到结束条件中 |
| 6 | 修复重置语句顺序导致充值中电梯被调度 | 开始和结束电梯RESET状态的语句应当在动作的最两侧 |
线程安全是线程安全类的属性,线程同步包含于具有线程同步功能的方法语义所自然说明的。线程安全和同步都应当被封装在类内部完成,以实现对使用者的透明化。
涉及应当建立在抽象层次划分的基础上,每一个模块的涉及应当只处理自己的抽象层次。