BUAA-OO-Unit2 Summary
第二单元博客作业——电梯月
第五次作业
这次的作业为多部多线程ALS电梯,每个人只能在同一座做纵向运动。但这个多部是一个伪多部。因为它对应的是A-E座的各个电梯,每个电梯之间相互独立,不全在交互,完全等价,就是同一座电梯复制5次罢了。
作业思路
这是第一次接触多线程,在写代码和调试的时候也是遭受到了无穷无尽的折磨。
先从对象设计的角度,本次作业主要是输入类和电梯类,其余的类基本都是为了保护线程安全而对原有的数据结构进行的改装。电梯固定了运行速度、开关门速度、允许人数。而对于电梯的调度策略,采用了类似基准策略所采用的ALS。
从多线程和共享资源的角度来看,采用了生产者消费者模式。我最开始的构架为有一个输入线程,一个调度器线程,五个电梯线程。输入线程和调度器线程共享一个等待队列requestQueue
,输入线程为生产者,调度器线程为消费者。里面存着input得到的投喂的请求。而调度器在获得请求后,根据其所在楼座放到每个电梯线程的等待队列中outside:Demand
。这个类是一个ConcurrentMap,是一个保证原子操作线程安全的Map。它的key值要上去的楼层,Value为request。But这样就会丢失请求添加的顺序,因此还需要另外一个RequesrQueue
(本质就是上锁的ArrayList加上一个停止标识)来依照时间顺序记录队列。这个Outsider和queue就是由调度器和电梯共享,此时调度器变为了生产者,电梯为消费者。结束进程时,除了要确认与其共享资源的进程结束了,也要确定共享队列是否为空,以电梯结束来看,其为
if (queue.isEmpty() && queue.isStop() && inside.isEmpty()) {
// nobody waits and waiting, nobody in the ele, nobody
break;
}
从具体实现的电梯的调度来看,首先是确定主请求,这与ALS一致
-
如果电梯内部为空,就再等待队列的队首
-
电梯内部有乘客,就选择最快到达,在本题下,就是电梯内部的请求的目标楼层和当前楼层距离最近的。
代码如下:
for (MyRequest request : outside.get(current)) {
// to see if there is someone can be taken with
// or the main request is waiting
if (num == capacity) {
if (outside.get(current).contains(mainRequest)) {
// reach the max num, need to change the main request
mainRequest = null;
selectMainRequest();
}
break;
}
if (high == 5 || isCarry(request)
|| (request.getDestination() < current && direction == -1)
|| (request.equals(mainRequest) && inside.isEmpty()) || mainRequest == null) {
// do something
} else if (request.equals(mainRequest) && (!inside.isEmpty())) {
// means mainRequest's direction is opposite to the current direction
// then we need to switch another mainRequest
mainRequest = null;
selectMainRequest(high);
}
}
再者是允许电梯载客的原则, 先聚焦在不超载的情况
- 对于当前楼层想要去往请求目标楼层的方向和当前电梯和电梯方向一致 (因为采用时即时交互,因此可以获得到的请求的到来时间必定不早于电梯到达的时间)
- 此请求为主请求,且电梯内部为空。
这样就会遇到一个情况,就是主请求要去的方向和电梯接到主请求的方向不一致,且电梯内部已经有人,比如一个主请求要求是FROM-5-TO-8, 点滴从第10层,它在下来的路上捎带了一些人,这些人的目标楼层均小于5层,我的处理方式,是从在电梯里面的那些人中重新选一个主请求。
此外,从上述表达中也可以看到,这里会存在着一种情况,就是电梯再接主请求的路上就已经满载了,这时应该即时更换主请求,要不然可能会导致主请求没有被接到。
但可能发现下面画的类图和我的这个思路略有不同,即缺少了调度器这个类,这是由于在调试中出现的问题造成的,再接下来会细说。
编写和调试
在同步块设置上,第一次接触多线程十分害怕出现线程安全问题。我秉承着“宁可慢,不要错”,在每个对共享资源进行操作时,无US论读还是写,都套上了synchronized
,而且有不少synchronized
套synchronized
方法,这样自然造成了性能的损失。
&emsp;在调试时,也遇到了许多问题,特别是不可复现的问题十分折磨心态。在开始写时,我不想用那么多的synchronized
,于是查了一些线程安全的类,但看到许多说法是这些类也并不是在任何情况下都是线程安全的,再加上后面总是存在的bug,我就将所有共享对象的方法都无脑加上了synchronized
,严重的降低了效率还并没有解决bug。
这次没有遇到轮询的问题,但是我在提交测试时总是出现了忽视输入的最后一个请求的,而这种情况在我的本机上无法复现。我咨询了一些曾经出现过这个bug的人,总点检查了我电梯结束的条件,但并没有任何收获。为了解决这个bug我几乎要把20次提交机会都用完了。在最后走投无路的情况下,我选了重构,把调度器删去(这一次作业调度器作用确实不太突出),由输入线程直接向电梯负责,才解决了这个问题。
PS:造成这个bug的问题,我在第六次作业中分析出来,并顺利解决了,在第六次作业的模块中会讲。
UML类图
本次题目较简单,可以看出类之间没啥继承等关系
流程图
代码度量分析
method metrics
method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
DDemand.Demand() | 0.0 | 1.0 | 1.0 | 1.0 |
Demand.containsKey(int) | 0.0 | 1.0 | 1.0 | 1.0 |
Demand.get(int) | 0.0 | 1.0 | 1.0 | 1.0 |
Demand.isEmpty() | 0.0 | 1.0 | 1.0 | 1.0 |
Demand.remove(int) | 0.0 | 1.0 | 1.0 | 1.0 |
Demand.size() | 0.0 | 1.0 | 1.0 | 1.0 |
Dispatcher.Dispatcher() | 0.0 | 1.0 | 1.0 | 1.0 |
Dispatcher.setNotify() | 0.0 | 1.0 | 1.0 | 1.0 |
Dispatcher.setStop(RequestQueue) | 0.0 | 1.0 | 1.0 | 1.0 |
Dispatcher.updateWaitingQueue(Elevator,RequestQueue,PersonRequest) | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.Elevator(int,String,RequestQueue) | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.addOutLine(PersonRequest) | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.addQueue(PersonRequest) | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.changeDirection(int) | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.queueNotifyAll() | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.setStop() | 0.0 | 1.0 | 1.0 | 1.0 |
Main.main(String[]) | 0.0 | 1.0 | 1.0 | 1.0 |
OutputThread.OutputThread() | 0.0 | 1.0 | 1.0 | 1.0 |
OutputThread.getInstance() | 0.0 | 1.0 | 1.0 | 1.0 |
OutputThread.println(String) | 0.0 | 1.0 | 1.0 | 1.0 |
RequestQueue.RequestQueue() | 0.0 | 1.0 | 1.0 | 1.0 |
RequestQueue.addRequest(PersonRequest) | 0.0 | 1.0 | 1.0 | 1.0 |
RequestQueue.isEmpty() | 0.0 | 1.0 | 1.0 | 1.0 |
RequestQueue.isStop() | 0.0 | 1.0 | 1.0 | 1.0 |
RequestQueue.removeRequest(PersonRequest) | 0.0 | 1.0 | 1.0 | 1.0 |
RequestQueue.setStop(boolean) | 0.0 | 1.0 | 1.0 | 1.0 |
RequestQueue.size() | 0.0 | 1.0 | 1.0 | 1.0 |
Demand.add(int,PersonRequest) | 2.0 | 1.0 | 2.0 | 2.0 |
Elevator.sleep(int) | 1.0 | 1.0 | 2.0 | 2.0 |
RequestQueue.earlyRequest() | 1.0 | 2.0 | 2.0 | 2.0 |
RequestQueue.first() | 1.0 | 2.0 | 2.0 | 2.0 |
Elevator.dealWithInside() | 4.0 | 1.0 | 3.0 | 4.0 |
Elevator.move() | 6.0 | 5.0 | 3.0 | 5.0 |
Dispatcher.run() | 8.0 | 3.0 | 4.0 | 9.0 |
Elevator.run() | 18.0 | 3.0 | 10.0 | 11.0 |
Elevator.selectMainReq() | 32.0 | 8.0 | 12.0 | 13.0 |
Elevator.dealWithOutside(boolean) | 21.0 | 3.0 | 16.0 | 16.0 |
Total | 94.0 | 56.0 | 83.0 | 93.0 |
Average | 2.5405405405405403 | 1.5135135135135136 | 2.2432432432432434 | 2.5135135135135136 |
class metrics
class | OCavg | OCmax | WMC |
---|---|---|---|
Demand | 1.1428571428571428 | 2.0 | 8.0 |
Dispatcher | 2.6 | 9.0 | 13.0 |
Elevator | 3.6666666666666665 | 10.0 | 44.0 |
Main | 1.0 | 1.0 | 1.0 |
OutputThread | 1.0 | 1.0 | 3.0 |
RequestQueue | 1.2222222222222223 | 2.0 | 11.0 |
Total | 80.0 | ||
Average | 2.1621621621621623 | 4.166666666666667 | 13.333333333333334 |
由此可见,主要是电梯类中的run方法和选择主请求与处理外部等待队列时耦合度过高,在写流程图时也可以感觉到,感觉在一些流程中,应该加某些方法更加独立的包装在不同的类中。
bug分析
本次互测和强测我自己没有bug,也没有测出别人的bug。其实比较躺平,发现了组里面有一些没有保证输出安全的代码,但是发现大家好像都没有办法在评测机的环境下测出来,就没有上交数据,但最后评测机重测之后是可以测出来的。
第六次作业
相比上一次作业,本次作业增加了可动态增加电梯和横向循环电梯的条件。请求除了纵向移动之外,还可以横向移动,但其移动限制在只能是纵向或横向,即起始楼座楼层和目的楼座楼层有且仅有一个不同。
作业思路
大体和上次一致,仍为生产者消费者模式。对于横向电梯我用了和纵向电梯类似的调度策略,其选择主请求和捎带原则都和纵向一致(这个为我第三次作业出现的bug买下了伏笔,之后会提及)。但因此为环形的循环电梯,选择方向和移动方式上与纵向略有不同。因此我将上次写的电梯稍微修改为抽象类,在选择方向和移动方向上用了抽象方法,在具体的纵向类和横向类中实现。此外,从上次作业可以看出我们可能需要用线程安全的List存着很多的不同类,如果对于每个这样的List都创建一个新的对象会使得代码的重复性增加,因此我的利用了泛型,构造了满足作业要求的队列,增加了可复用性。
public class LockArrayList<T> {
private final ArrayList<T> list;
// do something
}
这次作业中,我重新使用了调度器,对于可添加的电梯,我采用了HashMap存储它,key值为对应的楼座或楼层,value为这个电梯队列。之所以这样,我是采用平均分配的策略分配电梯,因此我又用了一个HashMap,其key值为楼层或楼座(楼座就是ascii值),value为i,意义为本次使用的是这个楼层(楼座)第i个电梯,在处理完楼层(楼座)的请求后,将其更新为
int count = (num + 1) % elevators.get(index).size();
/* num means the current number
* index indicates the floor or building
*/
counter.put(index, count);
此外,为了让纵向请求和横向请求统一,也为了让电梯类更好的复用,我自定了一个请求类,其将官方包的PersonReuqest
的组合进来,并且新设了from
和 destination
两个属性,如果为纵向请求,其值为[1, 10], 横向为[1, 5]代替了[A,E].
编写与调试
在两次作业中间,我翻阅了《图解java多线程设计模式》,对多线程增加了了解,对于何时加锁、依靠final避免一些加锁等方法也有了初步认识。这次作业中,我用了ReentrantLock
,对于会有线程读写并存的操作时加锁。更精细化的锁也使得我程序性能得到了提升。
起初,我按照第一次老是出现bug的方法来写,果然那个bug再次出现了。不过此时,OS课也讲到了进程的同步问题,老师分析是否存在进程安全问题的方法也教导了怎么样更好的静查bug。我也把检查范围扩大,思考着各种进程执行的顺序对我程序产生的影响。终于,我发现了其中的bug。其问题在于我输入线程和调度器线程的交互,以下图中,左图为输入线程,右图为调度器线程
这是出现bug的版本:
这是无线程安全的版本
分析原来的输入线程可以得知,考虑以下情况,此时已经处理完之前的请求,即等待队列为空,但后续还有数据需要投喂。当读取到最后一个输入时,输入线程将请求加入到共享队列后唤醒调度器,会得到null,于是就将标志结束,也同样要需要调度器。而再从调度器角度来看,在队列为空时,进入等待池,而后等待输入线程唤醒它。按照原来的写法,输入线程再加入请求后唤醒它,如果下一次是调度器得到了共享队列的锁,那么不会有问题,调度器处理完之后,再次进入等待池,然后被唤醒,发现已经结束了,再让各个电梯进程结束。但,当输入线程连续获得等待队列的锁时,那么输入线程在等待队列加入了请求后,又拿到了等待队列的锁,此时调度器无法处理等待队列中的请求,而之后输入结束,结束标记为true。但按照原来我的写法,那么被唤醒的调度器发现结束标志后,直接结束,忽略了等待队列此时不为空。因为这个代码段在if(queue.empty())
里,我想当然的认为这个属性得到了保证,却忽略了线程如果在其中释放锁到了等待池,再次唤醒时,共享队列的属性有可能出现改变,从而造成线程安全问题。
UML类图
因为横向电梯和纵向电梯大部分行为是一致的,我将其抽象化为有一个抽象类的电梯父类。
流程图
代码度量分析
method metrics
method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
Demand.containsKey(int) | 0.0 | 1.0 | 1.0 | 1.0 |
Demand.get(int) | 0.0 | 1.0 | 1.0 | 1.0 |
Demand.isEmpty() | 0.0 | 1.0 | 1.0 | 1.0 |
Demand.lock() | 0.0 | 1.0 | 1.0 | 1.0 |
Demand.remove(int) | 0.0 | 1.0 | 1.0 | 1.0 |
Demand.size() | 0.0 | 1.0 | 1.0 | 1.0 |
Demand.unlock() | 0.0 | 1.0 | 1.0 | 1.0 |
Dispatcher.Dispatcher(LockArrayList |
0.0 | 1.0 | 1.0 | 1.0 |
Dispatcher.chooseElevator(int,int,MyRequest) | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.Elevator(int,Character,int,int) | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.addOut(MyRequest) | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.addQueue(MyRequest) | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.getCurrent() | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.getId() | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.lock() | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.queueSignal() | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.setBuilding(Character) | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.setCurrent(int) | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.setDirection(int) | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.setFloor(int) | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.setStop() | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.unlock() | 0.0 | 1.0 | 1.0 | 1.0 |
ElevatorQueue.ElevatorQueue() | 0.0 | 1.0 | 1.0 | 1.0 |
ElevatorQueue.containsKey(int) | 0.0 | 1.0 | 1.0 | 1.0 |
ElevatorQueue.get(int) | 0.0 | 1.0 | 1.0 | 1.0 |
ElevatorQueue.lock() | 0.0 | 1.0 | 1.0 | 1.0 |
ElevatorQueue.size() | 0.0 | 1.0 | 1.0 | 1.0 |
ElevatorQueue.unlock() | 0.0 | 1.0 | 1.0 | 1.0 |
HorizontalElevator.HorizontalElevator(int,Character,int,int) | 0.0 | 1.0 | 1.0 | 1.0 |
Input.Input(LockArrayList |
0.0 | 1.0 | 1.0 | 1.0 |
LockArrayList.LockArrayList() | 0.0 | 1.0 | 1.0 | 1.0 |
LockArrayList.add(T) | 0.0 | 1.0 | 1.0 | 1.0 |
LockArrayList.get(int) | 0.0 | 1.0 | 1.0 | 1.0 |
LockArrayList.isEmpty() | 0.0 | 1.0 | 1.0 | 1.0 |
LockArrayList.isStop() | 0.0 | 1.0 | 1.0 | 1.0 |
LockArrayList.lock() | 0.0 | 1.0 | 1.0 | 1.0 |
LockArrayList.removeRequest(T) | 0.0 | 1.0 | 1.0 | 1.0 |
LockArrayList.setStop() | 0.0 | 1.0 | 1.0 | 1.0 |
LockArrayList.signalAll() | 0.0 | 1.0 | 1.0 | 1.0 |
LockArrayList.size() | 0.0 | 1.0 | 1.0 | 1.0 |
LockArrayList.unlock() | 0.0 | 1.0 | 1.0 | 1.0 |
Main.main(String[]) | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.getDestination() | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.getFrom() | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.getFromBuilding() | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.getFromFloor() | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.getPersonId() | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.getToBuilding() | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.getToFloor() | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.hashCode() | 0.0 | 1.0 | 1.0 | 1.0 |
OutputThread.OutputThread() | 0.0 | 1.0 | 1.0 | 1.0 |
OutputThread.getInstance() | 0.0 | 1.0 | 1.0 | 1.0 |
OutputThread.println(String) | 0.0 | 1.0 | 1.0 | 1.0 |
VerticalElevator.VerticalElevator(int, Character, int,int) | 0.0 | 1.0 | 1.0 | 1.0 |
VerticalElevator.changeDirection(int) | 0.0 | 1.0 | 1.0 | 1.0 |
Demand.add(int,T) | 2.0 | 1.0 | 2.0 | 2.0 |
Elevator.sleep(int) | 1.0 | 1.0 | 2.0 | 2.0 |
ElevatorQueue.add(int,T) | 2.0 | 1.0 | 2.0 | 2.0 |
LockArrayList.await() | 1.0 | 1.0 | 2.0 | 2.0 |
LockArrayList.first() | 2.0 | 2.0 | 2.0 | 2.0 |
LockArrayList.pop() | 2.0 | 2.0 | 2.0 | 2.0 |
MyRequest.MyRequest(PersonRequest) | 2.0 | 1.0 | 2.0 | 2.0 |
HorizontalElevator.move(int) | 2.0 | 3.0 | 3.0 | 3.0 |
Elevator.dealWithInside(int) | 4.0 | 1.0 | 3.0 | 4.0 |
Input.run() | 5.0 | 3.0 | 4.0 | 4.0 |
HorizontalElevator.changeDirection(int) | 6.0 | 1.0 | 3.0 | 5.0 |
MyRequest.equals(Object) | 3.0 | 3.0 | 3.0 | 5.0 |
VerticalElevator.move(int) | 6.0 | 5.0 | 3.0 | 5.0 |
Dispatcher.setStop() | 8.0 | 3.0 | 5.0 | 6.0 |
Dispatcher.run() | 30.0 | 4.0 | 11.0 | 11.0 |
Elevator.run() | 18.0 | 3.0 | 10.0 | 11.0 |
Elevator.selectMainRequest(int) | 28.0 | 8.0 | 11.0 | 12.0 |
Elevator.dealWithOutside(boolean,int) | 21.0 | 3.0 | 16.0 | 16.0 |
Total | 143.0 | 102.0 | 142.0 | 152.0 |
Average | 1.9324324324324325 | 1.3783783783783783 | 1.9189189189189189 | 2.054054054054054 |
class metrics
class | OCavg | OCmax | WMC |
---|---|---|---|
Demand | 1.1111111111111112 | 2.0 | 10.0 |
Dispatcher | 4.5 | 10.0 | 18.0 |
Elevator | 2.5555555555555554 | 10.0 | 46.0 |
ElevatorQueue | 1.1428571428571428 | 2.0 | 8.0 |
HorizontalElevator | 3.0 | 5.0 | 9.0 |
Input | 2.0 | 3.0 | 4.0 |
LockArrayList | 1.1428571428571428 | 2.0 | 16.0 |
Main | 1.0 | 1.0 | 1.0 |
MyRequest | 1.3 | 3.0 | 13.0 |
OutputThread | 1.0 | 1.0 | 3.0 |
VerticalElevator | 2.3333333333333335 | 5.0 | 7.0 |
Total | 135.0 | ||
Average | 1.8243243243243243 | 4.0 | 12.272727272727273 |
互测分析
本次测试没有被发现bug,也没有测出别人的bug。这次想着利用边界数据,看下能不能卡一个超时这种的,但是没有成功。
第三次作业
本次作业的变动主要在于两个方面,一是电梯可定制,可以定制其运行速度和限载人数,而对于横向电梯来说,还可以定制开门信息,即横向电梯不一定在每一个在每一座楼都可以开门;二是对于请求允许起始楼层和楼座都与终点楼层和楼座都不同,只要起点和终点不是同一个点就可以了。
作业思路
先考虑定制,这个实现起来很简单,直接在初始化时给电梯赋值就好了。这里可以采用不可变模式(Immutable), 将这些属性设置为final
。
接下来考虑换乘的事,我们将一个纵向电梯的运行和横向电梯的运行称作一次原子操作。我们大致可以可以将请求拆解为下图
其对应的java代码如下:
if (myRequest.getFromBuilding() == myRequest.getToBuilding()) {
chooseElevator(building, counter.get((int) building), myRequest);
} else {
int transparent = transparentFloor(myRequest);
int transparentFloor = transparent / 10000;
//get the transparentFloor
if (transparentFloor != myRequest.getFromFloor()) {
myRequest.setFrom(myRequest.getFromFloor());
myRequest.setDestination(transparentFloor);
chooseElevator(building, counter.get((int) building), myRequest);
} else {
chooseElevator(transparent, counter.get(transparent), myRequest);
}
有上述可知,一个完整请求最拆解成3个原子操作。
而上述可以发现,这次就不可避免的需要换乘这个操作的存在,这样对于电梯线程的停止的逻辑和前两次就不太一样。因为就算投喂的数据已经没有了对这层楼的电梯有任何请求,换乘中也可能会再次使用。在这里我借鉴了exp4-2的计数器单例模式,输入线程发现一个乘客请求就++,当电梯发现把请求送到目的地了就--,如果没有就再次加入的请求队列中。(其实应该把这个调度器也能从单例模式逻辑会更清晰一点,但我懒得改了呜呜呜)
// Thread input
if (request instanceof PersonRequest) {
Count.getInstance().increase();
}
// Thread elevator
request.update(high, current);
if (request.isArrive()) {
// update the request information and check whether arrive
Count.getInstance().decrease(); // count--
if (Count.getInstance().isCleared()) {
requests.lock();
try {
requests.signalAll();
}
finally {
requests.unlock();
}
}r
} else {
// if not arrive, then add a new request
PersonRequest personRequest = new PersonRequest(request.getTranFloor(),
request.getToFloor(), request.getTranBuilding(),
request.getToBuilding(), request.getPersonId());
requests.lock();
try {
requests.add(personRequest);
requests.signalAll();
}
finally {
requests.unlock();
}
}
相应的,调度器的终止也需要加上counter == 0
的判断
此外,对于可定制开关门的横向电梯,我将同一层楼,同样的关门信息的电梯都放在一个map里面,因此我的key值为(floor) << 5 + switchInfo
。而我选择中转楼层的方式,则是优先遍历起始楼层和终点楼层之间的楼层是否存在可作为中转的横向电梯,没有再像两侧衍生开。遍历楼层时,我的做法是将switchInfo从0-31其是否满足中转要求并且存在着这样的电梯。但这也会存在的毛病,就是如果一个人要从A-C, 有switchInfo为5和7的,这样就会一直选择5的,因此我记录下了上次选择的switchInfo,如只有这样的switchInfo满足条件那就用它,否则就换一个。
private int goThrough(int i, MyRequest myRequest) {
int key = i * 10000 + (myRequest.getFromBuilding()) * 100 + (myRequest.getToBuilding());
boolean flag = false;
/* true means find the only one (no other matches)
* false means there are no available
*/
for (int j = 1; j < 32; ++j) {
if (elevators.containsKey(i * 10000 + j)
&& buildingAvailable(j,
myRequest.getFromBuilding() - 'A', myRequest.getToBuilding() - 'A')) {
if (!switchArrangement.containsKey(key)) {
switchArrangement.put(key, j);
return j;
} else {
flag = true;
if (j != switchArrangement.get(key)) {
switchArrangement.put(key, j);
return j;
}
}
}
}
if (flag) {
return switchArrangement.get(key);
}
return 0;
}
编写和调试
这次编写没有遇到什么太大的问题,与这次代码与第六次作业相比改动不多有关。
UML类图
和上一次作业没有太大的差别,为了判断线程停止用了一个计数器单例模型,为了中转再调度器和自定义请求来中加入了一些属性和方法。
流程图
主要区别在于多了一个计数器单例进行交互,以及搜寻中转层
代码度量分析
method metrics
method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
Count.Count() | 0.0 | 1.0 | 1.0 | 1.0 |
Count.getInstance() | 0.0 | 1.0 | 1.0 | 1.0 |
Count.increase() | 0.0 | 1.0 | 1.0 | 1.0 |
Count.isCleared() | 0.0 | 1.0 | 1.0 | 1.0 |
Demand.Demand() | 0.0 | 1.0 | 1.0 | 1.0 |
Demand.containsKey(int) | 0.0 | 1.0 | 1.0 | 1.0 |
Demand.get(int) | 0.0 | 1.0 | 1.0 | 1.0 |
Demand.isEmpty() | 0.0 | 1.0 | 1.0 | 1.0 |
Demand.lock() | 0.0 | 1.0 | 1.0 | 1.0 |
Demand.remove(int) | 0.0 | 1.0 | 1.0 | 1.0 |
Demand.size() | 0.0 | 1.0 | 1.0 | 1.0 |
Demand.unlock() | 0.0 | 1.0 | 1.0 | 1.0 |
Dispatcher.Dispatcher(LockArrayList |
0.0 | 1.0 | 1.0 | 1.0 |
Dispatcher.buildingAvailable(int,int,int) | 0.0 | 1.0 | 1.0 | 1.0 |
Dispatcher.chooseElevator(int,int,MyRequest) | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.Elevator(int,Character,int,int,int,double,int,...) | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.addOut(MyRequest) | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.addQueue(MyRequest) | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.getAvailable() | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.getCurrent() | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.getId() | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.getSpeed() | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.queueSignal() | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.setBuilding(Character) | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.setCurrent(int) | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.setDirection(int) | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.setFloor(int) | 0.0 | 1.0 | 1.0 | 1.0 |
Elevator.setStop() | 0.0 | 1.0 | 1.0 | 1.0 |
ElevatorQueue.ElevatorQueue() | 0.0 | 1.0 | 1.0 | 1.0 |
ElevatorQueue.containsKey(int) | 0.0 | 1.0 | 1.0 | 1.0 |
ElevatorQueue.get(int) | 0.0 | 1.0 | 1.0 | 1.0 |
ElevatorQueue.lock() | 0.0 | 1.0 | 1.0 | 1.0 |
ElevatorQueue.size() | 0.0 | 1.0 | 1.0 | 1.0 |
ElevatorQueue.unlock() | 0.0 | 1.0 | 1.0 | 1.0 |
HorizontalElevator.HorizontalElevator(int,Character,int,int,int,double,int,...) | 0.0 | 1.0 | 1.0 | 1.0 |
HorizontalElevator.changeDirection(int) | 0.0 | 1.0 | 1.0 | 1.0 |
Input.Input(LockArrayList |
0.0 | 1.0 | 1.0 | 1.0 |
LockArrayList.LockArrayList() | 0.0 | 1.0 | 1.0 | 1.0 |
LockArrayList.add(T) | 0.0 | 1.0 | 1.0 | 1.0 |
LockArrayList.get(int) | 0.0 | 1.0 | 1.0 | 1.0 |
LockArrayList.isEmpty() | 0.0 | 1.0 | 1.0 | 1.0 |
LockArrayList.isStop() | 0.0 | 1.0 | 1.0 | 1.0 |
LockArrayList.lock() | 0.0 | 1.0 | 1.0 | 1.0 |
LockArrayList.removeRequest(T) | 0.0 | 1.0 | 1.0 | 1.0 |
LockArrayList.setStop() | 0.0 | 1.0 | 1.0 | 1.0 |
LockArrayList.signalAll() | 0.0 | 1.0 | 1.0 | 1.0 |
LockArrayList.size() | 0.0 | 1.0 | 1.0 | 1.0 |
LockArrayList.unlock() | 0.0 | 1.0 | 1.0 | 1.0 |
Main.main(String[]) | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.getDestination() | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.getFrom() | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.getFromBuilding() | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.getFromFloor() | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.getPersonId() | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.getToBuilding() | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.getToFloor() | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.getTranBuilding() | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.getTranFloor() | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.hashCode() | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.setDestination(int) | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.setFrom(int) | 0.0 | 1.0 | 1.0 | 1.0 |
MyRequest.toString() | 0.0 | 1.0 | 1.0 | 1.0 |
OutputThread.OutputThread() | 0.0 | 1.0 | 1.0 | 1.0 |
OutputThread.getInstance() | 0.0 | 1.0 | 1.0 | 1.0 |
OutputThread.println(String) | 0.0 | 1.0 | 1.0 | 1.0 |
VerticalElevator.VerticalElevator(int,Character,int,int,int,double,int,...) | 0.0 | 1.0 | 1.0 | 1.0 |
VerticalElevator.changeDirection(int) | 0.0 | 1.0 | 1.0 | 1.0 |
Demand.add(int,T) | 2.0 | 1.0 | 2.0 | 2.0 |
Elevator.isCarry(MyRequest) | 1.0 | 1.0 | 2.0 | 2.0 |
Elevator.sleep(int) | 1.0 | 1.0 | 2.0 | 2.0 |
ElevatorQueue.add(int,T) | 2.0 | 1.0 | 2.0 | 2.0 |
LockArrayList.await() | 1.0 | 1.0 | 2.0 | 2.0 |
LockArrayList.first() | 2.0 | 2.0 | 2.0 | 2.0 |
LockArrayList.pop() | 2.0 | 2.0 | 2.0 | 2.0 |
MyRequest.MyRequest(PersonRequest) | 2.0 | 1.0 | 2.0 | 2.0 |
MyRequest.isArrive() | 1.0 | 1.0 | 2.0 | 2.0 |
MyRequest.update(int,int) | 2.0 | 1.0 | 2.0 | 2.0 |
HorizontalElevator.move(int) | 2.0 | 3.0 | 3.0 | 3.0 |
Count.decrease() | 5.0 | 3.0 | 3.0 | 4.0 |
Dispatcher.dealWithElevator(Request) | 4.0 | 1.0 | 4.0 | 4.0 |
HorizontalElevator.calculateDistance(int,int) | 6.0 | 3.0 | 2.0 | 5.0 |
Input.run() | 8.0 | 3.0 | 5.0 | 5.0 |
MyRequest.equals(Object) | 3.0 | 3.0 | 3.0 | 5.0 |
VerticalElevator.move(int) | 6.0 | 5.0 | 3.0 | 5.0 |
Elevator.dealWithInside(int) | 10.0 | 1.0 | 5.0 | 6.0 |
Dispatcher.goThrough(int,MyRequest) | 13.0 | 6.0 | 7.0 | 7.0 |
Dispatcher.setStop() | 12.0 | 4.0 | 6.0 | 7.0 |
Dispatcher.transparentFloor(MyRequest) | 10.0 | 6.0 | 3.0 | 8.0 |
Dispatcher.run() | 27.0 | 4.0 | 11.0 | 11.0 |
Elevator.run() | 18.0 | 3.0 | 10.0 | 11.0 |
Elevator.selectMainRequest(int) | 29.0 | 8.0 | 12.0 | 13.0 |
Elevator.dealWithOutside(boolean,int) | 20.0 | 3.0 | 16.0 | 16.0 |
Total | 189.0 | 135.0 | 180.0 | 197.0 |
Average | 2.0543478260869565 | 1.4673913043478262 | 1.9565217391304348 | 2.141304347826087 |
class metrics
class | OCavg | OCmax | WMC |
---|---|---|---|
Count | 1.4 | 3.0 | 7.0 |
Demand | 1.1111111111111112 | 2.0 | 10.0 |
Dispatcher | 4.25 | 8.0 | 34.0 |
Elevator | 2.5789473684210527 | 10.0 | 49.0 |
ElevatorQueue | 1.1428571428571428 | 2.0 | 8.0 |
HorizontalElevator | 2.5 | 5.0 | 10.0 |
Input | 2.5 | 4.0 | 5.0 |
LockArrayList | 1.1428571428571428 | 2.0 | 16.0 |
Main | 1.0 | 1.0 | 1.0 |
MyRequest | 1.2352941176470589 | 3.0 | 21.0 |
OutputThread | 1.0 | 1.0 | 3.0 |
VerticalElevator | 2.3333333333333335 | 5.0 | 7.0 |
Total | 171.0 | ||
Average | 1.858695652173913 | 3.8333333333333335 | 14.25 |
和前两次作业差不太多,一些遗留问题没有咋更正,而是不断迭代,了,比如我想把电梯策略和电梯类分开,但最后也没有实现,导致了耦合度不断的上升。
互测分析
本次强测和互测都出现了一个bug,是相同的一个bug,是我横向电梯策略的不妥,导致的超时问题。而这个的起因,就在于懒。我的横向电梯是从电梯这个抽象类继承过来的,而在电梯抽象类中,我沿用了第五次作业的架构,沿用了捎带策略。但横向电梯是一个环形电梯,其捎带有关方向的判定不需要那么严格。(一圈圈绕着总会到的,况且总共才5座)。因此就出现了我的请求在横向电梯外面,一直等着电梯运行方向和目的地方向一致,白白浪费了时间,导致了超时。因此我将其改为横向电梯只要有想乘坐这个电梯的乘客,就都把他们接上这个电梯,修复了这个bug。
感想与总结
这次实验也算是感受到了多线程的险恶吧,一堆bug不可复现确实很搞心态。我第五次作业交了17 18次才通过的,那时候已经做好了无法进入互测的打算了,感谢帮助debug的同学和助教。也因为概念的不熟悉,翻阅了一些书籍,从中也学到了很多了知识。为了解决问题,还学习了一些Jprofile,Jconsole这种调试工具。对我帮助最大的一点还在于,开始在每天读一些技术方向的书籍,来提升拓展自己。
接下来半学期的课程大家一起加油!