301
社区成员
发帖
与我相关
我的任务
分享本次作业,对电梯进行建模是架构的核心。在我的作业中对电梯线程(第六、七次作业为控制器线程)的建模方法是根据电梯运行方向来运行一层。而在运行到新的一层后,会进行判断,是否有新的请求加入、是否需要进行重置、是否需要在该楼层停靠进行乘客上下、是否到达目的地并进行下次运行方向判断、是否请求队列为空该电梯需要等待、是否结束。在完成所有判断后电梯运行到下一层,在没结束之前一直进行该循环。
private void elevatorRun() throws InterruptedException {
if (elevator.getDirection() == 0) {
directionJudge();
} else if (elevator.getDirection() == 1) {
sleep(elevator.getSpeed());
elevator.up();
} else {
sleep(elevator.getSpeed());
elevator.down();
}
}
在多线程架构中,在对共享资源的访问中锁是十分重要的,而避免死锁也是作业中需要考虑的十分重要的问题。在我的作业中共享区主要有以下部分。InputThread线程和Schedule线程共享输入请求的等待队列,Schedule线程和Controller线程共享电梯的参数,如电梯当前所在楼层、电梯当前的请求队列等。对于请求队列,我的作业使用了实验代码中的RequestQueue类,其中所有方法均使用synchronized关键字进行修饰,请求队列的读写数目不是很多,因此对性能影响较小。
public synchronized Request getOneRequestAndRemove() {
if (isEmpty() && !isEnd) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (requests.isEmpty()) {
return null;
}
Request request = requests.get(0);
requests.remove(0);
notifyAll();
return request;
}
public synchronized void addRequest(Request request) {
requests.add(request);
notifyAll();
}
在第六七次作业中,因为控制器线程和调度器线程要同步分享电梯的各项参数和标记变量,读写次数大量增加,如果继续采用synchronized关键字会导致损失较大,因此采用了读写锁的方法来进行同步控制。对于电梯信息改变的情景,如上下乘客、进行重置等,需要加写锁;而对于调度去读取电梯信息,则需要加读锁。锁要及时释放从而避免产生死锁。
对于第五次作业,只需要实现单个电梯的调度策略,同样该策略也适用于第六、七次作业的单个电梯。采用的策略是电梯具有方向和目的地,根据这两个变量到每一层后进行判断是否有可以捎带的乘客,如果有则加入电梯的乘客队列。在到达目的地后如果等待队列中存在请求则重新判断电梯运行方向,没有请求则线程等待。
对于第六、七次作业调度器的综合调度策略,我采用的是优先级打分策略。当调度器接收到一个请求后,会根据请求楼层和当前每个电梯的参数来对每个电梯进行一个优先级打分,最后将请求加入到得分最高的电梯的等待队列。
if (elevators.get(i).getDirection() == 0 && elevators.get(i).getEmpty() == 1) {
p = 40 - 0.2 * elevatorRequests.get(i).getSize() - 0.1 * Math.abs(elevators.get(i).getFloor() - fromFloor) - 0.001 * elevators.get(i).getSpeed();
} else if (direction == elevators.get(i).getDirection() && ((fromFloor - elevators.get(i).getFloor()) * direction > 0)) {
if ((toFloor - elevators.get(i).getDestination()) * direction < 0) {
p = 30 - 0.2 * elevators.get(i).getPCnt() - 0.1 * Math.abs(elevators.get(i).getFloor() - fromFloor) - 0.001 * elevators.get(i).getSpeed();
} else {
p = 10 - 0.2 * elevators.get(i).getPCnt() - 0.1 * Math.abs(elevators.get(i).getFloor() - fromFloor) - 0.001 * elevators.get(i).getSpeed();
}
} else {
p = -0.2 * elevators.get(i).getPCnt() - 0.1 * Math.abs(elevators.get(i).getDestination() - fromFloor) - 0.001 * elevators.get(i).getSpeed();
}
对于第七次作业的双轿厢电梯,我将其分为两个电梯分别打分。如果出现请求的楼层范围和双轿厢电梯的运行范围不匹配,电梯会在换乘楼层将乘客放下并重新将请求返回调度器重新调度。

本次作业架构较为简单,main启动输入、调度器和电梯线程,输入线程获取请求并加入到调度器的请求队列,然后调度器从请求队列中获取请求并将其分配到指定电梯的请求队列中。电梯只根据电梯本身的请求队列和当前状态运行,单电梯的调度策略在电梯线程内实现。协作图如图所示:


本次作业中,因为调度器线程要读取电梯的状态相关变量,为了避免线程之间的相互读取,将电梯变为普通的类。针对每个电梯设置一个控制器线程来控制电梯的运行,电梯类中只存放电梯状态相关的变量,通过控制器线程来修改电梯的状态并模拟电梯的运行,调度器线程也是从电梯类中读取状态变量,从而避免了线程间的互相读取。
对于Receive的实现,对每个电梯增加了inList,在电梯确定去接送请求对应的人后会将请求加入inList并输出Receive,而未确定接送的请求则是放入等待队列。
对于Reset的实现,对电梯类设置Reset变量来判断电梯是否处于Reset状态。同时调度器在接收到Reset请求时,会将该请求置为请求队列中的第一个请求并分配到相应电梯,从而确保了最长会在电梯运行一层的时间内来进行Reset。Reset时电梯会在当前楼层将乘客放下,并将请求修改后重新加入调度器的请求队列来进行重新调度。
为了避免多部电梯同时Reset使某一电梯请求过多,设置一个电梯的请求数不会超过其最大载重人数。同时,如果其他电梯空闲且某一电梯请求较多,会将请求较多的电梯中等待请求返回调度器重新调度。协作图如图所示:


本次作业中增加了双轿厢电梯,实现方法是在接收双轿厢重置请求的时候新增一个电梯类和对应的控制器线程,并将两个电梯的双轿厢标志量设为是,然后对两个电梯进行正常的重置。调度器请求面对双轿厢电梯时,如果请求的出发和到达楼层都在双轿厢无法到达的一侧,则不会给该电梯分配该请求,其余情况按正常电梯调度策略分配。如果出现请求跨越了换乘楼层,双轿厢电梯会在换乘楼层将乘客放下并将请求修改后加入到调度器等待队列重新调度。
对于双轿厢电梯的防碰撞问题,双轿厢电梯的两个电梯会共享一个变量来确定对方是否在换乘楼层。每个双轿厢电梯会在进入换乘楼层的前一层进行判断,如果对方在换乘楼层则等待,不在则进入,只需给共享变量设置一个读写锁则能解决碰撞问题。协作图如图所示:

第五次作业主要的bug是电梯没有正常进入休眠从而轮询导致CTLE问题,在debug中采用了助教在群里提供的针对CTLE的问题来进行解决。
第六次作业的bug第一是如果部电梯同时Reset使某一电梯请求过多,导致运行时间过长。解决方法是设置一个电梯的请求数不会超过其最大载重人数。同时,如果其他电梯空闲且某一电梯请求较多,会将请求较多的电梯中等待请求返回调度器重新调度。
第二是控制器线程会偶尔出现无法停止的问题,且在本地无法复现该bug。在尝试多次无法复现后尝试的解决办法是重新修改了控制器线程和调度器线程的结束逻辑,尽量保证在极限情况下也不会出现无法停止的问题。
第三是Reset请求实现重置时间较晚,解决方式是调度器在接收到Reset请求时,会将该请求置为请求队列中的第一个请求并分配到相应电梯,从而确保了最长会在电梯运行一层的时间内来进行Reset。
第七次作业的bug第一是双轿厢相撞问题,通过两个电梯共享变量设置读写锁从而解决了问题。
第二是因为双轿厢重置输出重置结束的是原电梯,所以出现了双轿厢新电梯开始Receive但旧电梯还未输出重置结束的问题。解决方法是让新电梯sleep很短的一段时间后再将自己重置参数置于完成。
第二单元作业是我首次接触到多线程,因为多个线程间共享变量的原因,让我对线程安全有了更多的认识。线程安全是多线程中可以说是十分重要的一部分,如果锁的使用不正确,可能会导致数据冲突或者死锁,这都是多线程中十分常见的问题。因此在代码编写过程中应着重考虑这方面的问题,进行层次化的设计,了解代码的逻辑才能更好的进行多线程的编码。因此初期设计仍是十分重要的一环,结构清晰还能更好的维护和迭代。
同时面对无法复现的bug,在作业中给我造成了很大的困扰,也让我认识到了自己对于线程间交互的考虑还不够多。之后如果还有机会进行多线程的编码,应多考虑下多线程交互过程中可能出现的bug,在初期就解决可能出现的问题。同时在本单元中我也学会的很多有用的设计模式,收获很大。