301
社区成员
发帖
与我相关
我的任务
分享本次作业要求实现六部电梯,按需将乘客分配,直到运行结束。


本次作业几个线程的共享资源是总waitingList,因此在waitingList类中,需要将它的方法上锁。
在getOneAndRemove方法中,如果总表中暂时不存在request,那么需要wait。直到addRequest方法成功添加,then notifyAll()即可。
在setEnd方法中,为了保证线程正常运行和结束,需要添加notifyAll语句。
本次作业的乘客所需电梯已经给出,因此直接分配即可。
本次作业最重要的就是电梯的运行策略,我采用的是look策略。
在我的strategy类中,通过nextDirection方法判断电梯下一步的运行方向。
int next = 0;
ArrayList<PersonRequest> f = waitingList.getPersonRequests();
if (!inside.isEmpty()) { //如果电梯内有人,那么仍按照原方向运行
next = direction;
} else {
int p = 0;
for (int i = 0; i < f.size(); i++) { /*如果电梯内没人,那么遍历waitList,如果有乘客目标方向与当前电梯运行方向相同的,方向不变 */
int z = f.get(i).getToFloor() - f.get(i).getFromFloor();
if ((f.get(i).getFromFloor() > currentFloor && direction == 1) ||
(f.get(i).getFromFloor() < currentFloor && direction == -1) ||
(f.get(i).getFromFloor() == currentFloor && z * direction > 0)) {
next = direction;
p = 1;
break;
}
}
if (p == 0) {
next = -direction; //否则,方向改变
}
}
// 以下为最底层和最顶层的特殊处理
if (currentFloor == 1 && next == -1) {
next = 1;
}
if (currentFloor == 11 && next == 1) {
next = -1;
}
return next;
在电梯线程中,while循环内的步骤如下:
1)判断是否return,如不,则判断是否需要wait。
2)direction = strategy.nextDirection();
3)出人、进人
4)再次判断是否return,如不,则判断是否需要wait。
5)direction = strategy.nextDirection();
6)输出arrive
在本次作业中,强测遇到了bug,主要问题出在电梯进人时,需要利用迭代器对电梯的状态waitingList进行边遍历边删除,这时应该对这个操作加锁。否则,如果这时别的线程在向waitingList中加入元素,就会导致同步编辑的报错。
本次作业新增了reset请求,同时需要自己设计调度器将乘客合理分配给各个电梯。


本次作业新增了ResetList类,每个电梯配备有候乘表waitingList,reset表resetList,以及两者的合表bothList。bothList元素的增添与删减都与前两者保持同步。
在电梯线程“判断是否return,如不,则判断是否需要wait”这个步骤中,由于bothList是电梯与其他线程的主要共享资源并且是判断return或wait的重要条件,为保证线程安全,我将整个过程套上了bothList锁。
此外,在我的调度线程中,如果选择分配的电梯暂时在reset,那么我将这个请求加入该电梯的暂存数组notReceive中。在电梯reset结束后,再统一receive。在这个过程中,为避免同时读写的情况,需要将notReceive上锁。
本次作业的调度综合考虑了多种因素,具体实现如下:
首先去掉已经达到限乘人数的电梯:
如果都已经达到限乘人数,那么直接从6部电梯中,挑选运行方向与乘客的目标方向一致,满足捎带条件的电梯:如果存在,则选择Math.abs(lifts.get(i).getCurrentFloor() - getFromFloor) + (waitingList.size() + inside.size() + notReceive.size()) * 0.6数值最小的电梯;如果不存在,则选择waitingList.size() + inside.size() + notReceive.size()最小的电梯。
如果存在没达到限乘人数的电梯,则从6部电梯中,挑选运行方向与乘客的目标方向一致,满足捎带条件的电梯:如果存在,则选择Math.abs(lifts.get(i).getCurrentFloor() - getFromFloor) + (waitingList.size() + inside.size() + notReceive.size()) * 0.6数值最小的电梯;如果不存在,则选择waitingList.size() + inside.size() + notReceive.size()最小的电梯。
本次作业的debug过程比较漫长,bug主要集中在两个方面。
首先就是在电梯reset处理时,应将所有人从当前楼层赶出,这时应注意的是,应该将inside中的乘客放入waitingList作为缓冲,在reset过程中再将waitingList中所有请求都放回allList。如果直接放回allList,就可能导致在reset begin之前提前receive。
其次就是在调度器的结束判断条件上。由于reset的出现,在allList end之后可能仍有新请求加入其中。在改为最终版本之前,我使用了一个特殊容器,其作用是真正保留未完成的请求,并且将调度结束条件写成allList空且end并且特殊容器空,但这样以后仍会有程序结束不了情况的出现,我最后也是没能彻底解决这个问题,可能还是由于我设计的这个特殊容器考虑不够周全的缘故。最后,我将调度的结束条件改为allList end & empty,且所有电梯的ifReset均为0,才克服了这个问题。
本次作业新增了双轿厢电梯。要求如下:
双轿厢电梯是指在同一电梯井道内同时拥有两个独立的电梯轿厢,而电梯系统默认的普通电梯是指在一个电梯井道内只有一个轿厢。为了保证两个轿厢不相互碰撞,将楼层分为上区、下区、换乘楼层,其中上区为换乘楼层以上的所有楼层,下区为换乘楼层以下的楼层,均不包含换乘楼层。在整个运行过程中,要求轿厢 A 只能在下区和换乘楼层运行,轿厢 B 只能在上区和换乘楼层运行,同一井道内的两轿厢不能同时位于换乘楼层。请思考双轿厢电梯的优势(包括双轿厢电梯耗电量优势,具体定义见性能分说明部分),合理设计调度方案。


对于双轿厢电梯电梯的加入,我在原电梯中加入了两个属性,即电梯A和电梯B。在原电梯处理DCreset请求时,我会开启两个新电梯的线程。
本次作业主要利用线程解决这个问题,每个电梯都有一把专用锁lock。
首先,在电梯满足移动条件时,如果下一步就走到换乘楼层并且此时另一电梯处在换乘楼层,那么就要wait。如果当前确实处于换乘楼层,那么正常运行,同时notifyAll,以唤醒处于等待的电梯。这些过程都要上锁。
其次,在电梯处于正常等待时,在这之前要判断电梯是否处在换乘楼层,如果是,那么就需要往下或往上移动一层,然后再开始正常等待。这个过程也要为lock加上锁。
本次作业由于时间原因,没有像前一次作业那样设计一个更加合理的调度策略,采用的是均分策略。
本次作业和前一次作业一样,遇到了不少bug。
1)在判断调度结束的条件时,我写了一个方法endJudge,用来判断所有电梯的所有表是否都空。其中,notReceive数组也考虑到了其中。但是,我将notReceive是否为空写在了整个方法的最后,所以会出现这种情况:在电梯将notReceive分发给自己或两个子电梯之前,由于还没加,所以endJudge方法判断了电梯的候乘表可能为空,但是在endJudge判断到notReceive时,己经加完了,这时notReceive就会判定为空,这会导致结束条件成立,从而提前结束。为了解决这个bug,我将notReceive是否为空移动到了前面。除此之外,endJudge中的判断顺序均须按照逻辑进行排列,才能避免提前结束的情况。
2)在电梯运行时,遇到出人的情况,应该先把人加入新队列中,然后再从原队列删除,否则也会像上面一样出现提前结束的情况。
3)在判定电梯是否结束时,我开始的操作是
if (inside.isEmpty() && bothList.isEmpty()) {
synchronized (allList) {
allList.notifyAll();
}
if (bothList.isEnd()) {
return;
}
} // 1
if (inside.isEmpty() && bothList.isEmpty() //2) {
bothList.await();
}
这可能导致在位置1之前判定bothList没有end,但在位置1处bothList被setEnd了,这样会导致电梯一直等下去,导致超时。所以应该在位置2处加上&& !bothList.isEnd(),这样才能避免一直等待的情况。
在面对wrong_answer时,针对样例,比较容易定位到错误所在,直接改动即可;
在面对C_TLE时,可能是轮询的问题,问题大概率出在调度器中,应该在get请求时,避免无效的wait;
在面对R_TLE时,可能是由于结束不了的缘故,或是调度策略。在解决前者时,可以通过print来判断是否走到了结束这一步。
总的回顾这三次作业,我认为稳定的内容是总的架构、输入模式等。易变的内容是电梯自身的运行策略以及操作形式。比如,在三次作业中,对于WaitingList类中的方法、输入线程的编写、以及总的框架变化不是特别大。变更的主要是细节。特别是在增加reset后,总表的结束并不意味着真正的结束,因为后续可能还会有请求再度加入其中。这时候就需要在调度器及电梯的结束条件上加以改变。此外,调度策略也是随着要求的不同而改变的。
在这单元的作业中,我花了很长时间与锁、等待、唤醒这一系列问题较劲。
在线程安全方面,对于锁的设置是非常必要的,越往后的共享资源越多,锁的设置就越复杂,如果设置不好,会导致很多不可复现的bug。此外,在应对轮询、持续等待等问题时,还是需要静下心来分析整个过程,及时的notifyAll处于等待的线程。再者,要合理分析if-act之间的间隙,适当的加上锁以防止条件突然变更的情况。
在层次化设计方面,本单元进一步提升了我对于层次化的认识。多线程问题,既是集中的,也是分散的。单独设计好每个线程,同时考虑清楚所有线程之间的共享资源的关系,两者都很重要。
总而言之,这单元的学习令我印象深刻,也学到了很多东西。希望在接下来的学习中,能更加顺利。