301
社区成员
发帖
与我相关
我的任务
分享本单元作业我只有在hw_5中设置了调度器,并只有简单的按电梯id分配请求,故不再赘述
hw_6,hw_7均采用自由竞争的策略,即不实施调度,各电梯从总队列中自由获取请求
第一次作业要求较为简单,故每个电梯都各自有一个候乘表,仅需要考虑这个表里的请求如何处理即可,所以重点关注电梯的运行策略。
以下是UML类图和协作图


Look策略Elevator.java
while (true) {
end();//判断是否结束
//passengers.isEmpty() && allListEle.isEnd() && allListEle.isEmpty()
strategy.reInitial(this);//为策略类更新电梯状态
end();
ups = strategy.getUps();//策略类给出要上电梯的请求
downs = strategy.getDowns();//策略类给出要下电梯的请求
direction = strategy.getDirection();//策略类给出电梯接下来的方向
boolean e = false;
if (ifOpen(ups,downs)) {//判断是否开门
open();//开门
off(downs);//下电梯
on(ups);//上电梯
strategy.reInitial(this);//再次更新状态(判断此时是否可能转向)
direction = strategy.getDirection();
ups = strategy.getUps();
on(ups);//新请求||因转向而可捎带的请求,上电梯
close();//关门
}
end();
strategy.reInitial(this);
direction = strategy.getDirection();
end();
move();//移动一层
}
strategy.reInitial(this)。将电梯的动作和对当前状态的分析决策分开。电梯不用关心目前有什么请求,只需要根据策略执行固定的动作。策略类也只需要把电梯传进来的信息做分析并返回决策即可。Srategy.java
public void reInitial(Elevator elevator) {
this.currentFloor = elevator.getCurrentFloor();
this.waitingListEle = elevator.getWaitingListEle();
this.allListEle = elevator.getAllListEle();
this.passengers = elevator.getPassengers();
this.direction = elevator.getDirection();
ups.clear();
downs.clear();
setWait();//判断电梯是否需要等待,若需要则直接在这里等待
setDirection();//判断是否转向
setDowns();//决定下电梯的人
setUps();//决定上电梯的人
}
这次作业不再限制请求要乘坐的电梯,并加入了
Reset请求。
从这次作业开始,我实行自由竞争策略,故移除了Schedule,没有新增类。这次作业是改动最大的一次,相当于重构了。
以下是UML类图和协作图


同步块的设置和锁的选择标题中叙述allList队列,队列中包含所有类型的所有(包括其他电梯的Reset)请求,这几个队列将保证时刻同步allList的所有操作,都遍历对所有6个执行Input中无条件加入allList,只有乘客上电梯时或Reset执行完毕前从中移除相应请求,在Reset赶出未完成的请求时,重新加入6个allListallList参与构成每个电梯的结束条件,即当allList空时,可以确定所有请求处理完成(上了电梯的也不会下电梯),因为Reset请求在处理完毕前才删除,若有请求被赶出,应先加入allList,保持了allList的不空状态,不会提前结束resetList队列Input中来Reset请求时,应(通过锁)保证同步加入allList和相应的resetList。避免被allList唤醒却发现没有Reset请求的错误resetList不空时,电梯尽早重置floorList.get(i)合并,变为总的floorListReset请求的处理Reset请求和Person请求视为同等请求均放入allList中Reset的处理视为电梯的动作之一 multiple.readLock().lock();//////////////multiple
if (!resetList.isEmpty()) {
multiple.readLock().unlock();////////////////multiple
reset();//执行重置函数
continue;//相当于回到初始状态
}
multiple.readLock().unlock();//////////////multiple
reset()具体实现 如果电梯不空,“赶出”乘客;//加回allList
"RESET_BEGIN";
将已`Receive`的请求释放掉;
移除Reset请求;
sleep(1200);
置电梯属性;
"RESET_END";
passengers.get(i).setFromFloor(currentFloor);
passengers.get(i).setDispatched(0);
passengers.get(i).setPrinted(false);
由于本次课程组的限制,要求打出
RECEIVE以鼓励实现调度器,避免陪跑,故实现自由竞争较为困难,没有RECEIVE的电梯不能移动,被RECEIVE的乘客必须乘坐指定电梯。
RECEIVE的电梯可以自由移动Person请求类添加isDispatched和isPrinted属性,分别标志分配给哪个电梯(0代表未分配,k代表分给id为k-1的电梯) 和是否打印RECEIVEisPrinted决定是否打印RECEIVEwait,只需在wait的条件中加入一个新条件judge() while (judge() && !(allList.get(elevatorId - 1).isEnd() &&
allList.get(elevatorId - 1).isEmpty())) {
allList.get(elevatorId - 1).wait();
}
judge()的思路 public boolean judge() {
遍历allList.get(elevatorId - 1){
遇到乘客请求(isDispatched()==elevatorId + 1){
根据isPrinted()是否打印:
return false;//不等待
}
遇到自己的Reset请求{
return false;//不等待
}
}
再次遍历allList.get(elevatorId - 1){
遇到乘客请求(isDispatched()==0){
setDispatched(elevatorId + 1);//抢请求
打印RECEIVE;
setPrinted(true);//标记已打印
return false;//不等待
}
}
return true;//最后得出要等待
}
RECEIVE,获得移动权,只要运行起来,接下来就可以实现自由竞争了加入第二类重置请求以及双轿厢。架构几乎没有变化,为策略和电梯实现了接口。
以下是UML类图和协作图



DcReset请求的处理NormalReset一样将DcReset请求和其他请求视为同等请求均放入allList中NormalReset共同放入resetListDcReset的具体处理与NormalReset相同,视为电梯的动作之一 multiple.readLock().lock();//////////////multiple
if (!resetList.isEmpty()) {
multiple.readLock().unlock();////////////////multiple
if (choose()) {
resetNormal();
} else {
resetDc();
return;//关闭电梯
}
continue;
}
multiple.readLock().unlock();////////////////multiple
resetDc()具体实现 如果电梯不空,“赶出”乘客;//加回allList
"RESET_BEGIN";
将已`Receive`的请求释放掉;
移除Reset请求;
sleep(1200);
置电梯属性;
"RESET_END";
this.elevatorA.start();//启动A轿厢
this.elevatorB.start();//启动B轿厢
Elevator接口,下接NormalElevator和DcElevator两种电梯Strategy接口,下接NormalStrategy、DcStrategyA和DcStrategyB三种策略,本质都是look策略,细节处理有所差异。这样不用修改过多代码即可实现双轿厢Main中创建18个电梯线程(6个normal,12个Dc),并创建好各自的策略,传入对应电梯,随即启动6个Normal电梯,其余12个等待启动allList也变为18个,启动后的A、B轿厢和其他单轿厢电梯地位相同,共同竞争DcElevator在每到达一层开门下完人后,判断是否为换乘楼层,若是且有乘客则赶人。注意先加入allList再清空乘客容器,防止两队列全空的间隙,另一个轿厢提前结束(见下一条)DcElevator在换乘楼层赶人增添新请求,影响到其他电梯的结束,故我让其另一个轿厢监管这个请求,防止请求被遗弃,即将DcElevator的结束条件新增一项fellow.getPassengers().isEmpty()//另一个轿厢乘客空
DcStrategy细节上的修改,不赘述,如何防撞在下面专题介绍由于后两次作业采用了自由竞争的策略,故线程安全较难保证,于是我设置了较为复杂的锁系统,自认为还算安全,但由于锁太多,等待锁的时间较长,所以最后的性能分也不算很高,但也很不错了。
| 锁 | 类型 | 作用 |
|---|---|---|
| multiple | ReentrantReadWriteLock | 保证18个allList,floorList以及resetList的同步修改 |
| allList.get(i) | 对象锁 | 保证在遍历某个allList时,其不能被改变 |
| rwLock | ReentrantReadWriteLock | 共有11个,维护11个每层的请求队列floorList.get(i) |
| personLock | ReentrantLock | 维护Person请求中isPrinted,isDispatched属性 |
| DcLock | ReentrantLock | 防止轿厢碰撞 |
multiple > allList.get(i) > personLock
multiple > rwLock > personLock
allList.get(i) > DcLock
personLock是电梯自由竞争的关键,能避免多个电梯同时抢到同一乘客,优先抢到锁的电梯才能修改请求的isDispatched属性为自己的标志Input.java if (request instanceof NormalResetRequest) {//读入新的Reset请求
Reset reset = new NormalReset(*);
multiple.writeLock().lock();//保证18个`allList`和`resetList`的同步修改
for (int i = 0; i < 18; i++) {
allList.get(i).addRequest(reset);
}
resetList.get(((NormalResetRequest) request).getElevatorId() - 1).addRequest(reset);
multiple.writeLock().unlock();
NormalElevator.java public void on(ArrayList<Person> ups) {//上电梯
if (!ups.isEmpty()) {
for (int i = 0; i < ups.size() && passengers.size() < capacity; i++) {
if (!ups.get(i).getPrinted()) {
TimableOutput.println(String.
format("RECEIVE-%d-%d", ups.get(i).getPersonId(), id));
}
TimableOutput.println(String.
format("IN-%d-%d-%d", ups.get(i).getPersonId(), currentFloor, id));
passengers.add(ups.get(i));//不共享,不用维护
multiple.writeLock().lock();//保证18个`allList`和`floorList`的同步修改
writeLockListOn.get(currentFloor - 1).lock();//维护floorList.get(currentFloor - 1)
for (int k = 0; k < 18; k++) {
allList.get(k).removeRequest(ups.get(i));
}
floorList.get(currentFloor - 1).removeRequest(ups.get(i));
writeLockListOn.get(currentFloor - 1).unlock();//释放
multiple.writeLock().unlock();//释放
}
}
}
本单元作业中涉及多处check-then-act线程安全隐患,研讨课上有同学分享并没有维护也没有出问题,的确这种问题出现bug的几率极小,但我认为也是必须要维护的
synchronized (allList.get(elevatorId - 1)) {
while (judge() && !(allList.get(elevatorId - 1).isEnd() && allList.get(elevatorId - 1).isEmpty())) {
allList.get(elevatorId - 1).wait();
}
}
//////////////比较
while (judge() && !(allList.get(elevatorId - 1).isEnd() && allList.get(elevatorId - 1).isEmpty())) {
synchronized (allList.get(elevatorId - 1)) {
allList.get(elevatorId - 1).wait();
}
}
while判断,判断为真后,很可能其值被修改为假,然而此时已不可避免的执行wait(),导致最后无法被唤醒;还有可能在多条件判断时,前面条件判断时,后面的条件可能会被修改,造成误判。我的设计是:
Dc电梯即将前往换乘楼层时,询问另一轿厢是否正处在换乘楼层,是则等待,否则正常运行Dc电梯正常移动前,判断是否处在换乘楼层,是则唤醒另一轿厢(无论其是否在wait中)Dc电梯因缺乏请求而即将wait()前,先强行将其移离换乘楼层,随后唤醒另一轿厢,再wait(),防止占用换乘楼层进行等待具体实现则用到了上面提到的DcLockDcElevator.java - move()
//例,此时若方向向上
synchronized (lock) {//DcLock
if (type == 'A' && currentFloor == transferFloor - 1 && fellow.getCurrentFloor() == transferFloor) {
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
sleep(*);
TimableOutput.println(String.format("ARRIVE-%d-%d-%c", currentFloor + 1, (id - 5) / 2, type));
currentFloor++;
if (type == 'B' && currentFloor == transferFloor + 1) {
lock.notifyAll();
}
}
if都要在锁中,保证一个轿厢移动完,另一个轿厢才开始判断,先判断再阻塞没有意义currentFloor++;放在锁内,保证释放锁的时候,楼层已修改,否则另一轿厢得到的信息会延迟三次作业只有第二次强测出现了bug,未RECEIVE就移动,可以确定是线程安全问题引发的WA,但经多次检查,并没有发现哪里不安全,但定位到了具体出问题的代码,于是加了一个特判,补上了RECEIVE,后续也没有新的bug,但还是没从根源解决问题,一直很苦恼。
wait()失效,导致轮询wait()无法被唤醒最大的体会就是,理解和掌握线程安全和各种锁的使用非常重要。虽然没有在调度器以及调度策略上费工夫(卷性能),但在设计各种锁,维护我的自由竞争的一大坨线程安全问题的时候,也绞尽脑汁,在这个过程中,我对锁的理解有了进一步的提升。