前言:
本单元的作业是电梯合集,傻瓜调度、ALS捎带以及多部智能调度电梯。傻瓜调度的核心是每次只能处理一个请求,且为最先到达的请求;ALS捎带则为相对主请求可捎带的(有点儿傻)电梯,可捎带原则为,发出的请求楼层与主请求楼层为当前楼层的同一侧;多部智能调度电梯则为多部电梯可同时运行,并加入电梯容量、电梯可停靠楼层、运行速度等独立属性,以及中转转乘机制。综合来看,本单元主要是对多线程及线程安全的考察。这次相比之前多加了输入输出借口,输入不用像第一单元那样魔鬼处理(wf判定)了,并且输出是一种新的带有时间戳的形式,且程序运行时间暴涨,评测机压力直线上升(滑稽)
一、三次作业具体分析
第一次作业:
1、设计策略
第一次作业较为简单,所以设计的较为简单,除MainClass类外,仅有一个线程类Elevator。
MainClass类为循环读入PersonRequest,并将请求的目标楼层(不涉及捎带等其他要求,且为逐个处理,故不用考虑请求楼层)放入等待队列list,并以参数形式传给Elevator类:
1 public class MainClass { 2 public static void main(String[] args) throws IOException { 3 List<Integer> list = new ArrayList<>(); 4 ElevatorInput elevatorInput = new ElevatorInput(System.in); 5 while (true) { 6 PersonRequest request = elevatorInput.nextPersonRequest(); 7 if (request == null) { 8 break; 9 } else { 10 Elevator elevator = new Elevator(); 11 elevator.getFactors( 12 request.getFromFloor(), 13 request.getToFloor(), 14 request.getPersonId()); 15 elevator.getList(list); 16 elevator.run(); 17 list.add(request.getToFloor()); 18 } 19 } 20 elevatorInput.close(); 21 } 22 }
然后在Elevator类中对出入电梯以及更改楼层进行模拟,利用sleep方法模拟所需时间:
1 private void spend(long time) { 2 try { 3 Thread.sleep(time); 4 } catch (InterruptedException e) { 5 e.printStackTrace(); 6 } 7 }
只是在起始楼层非1时以及处理完当前请求并更改楼层到下一请求时有些许处理,其他并无难点。
2、方法复杂度等分析
通过相关数据可知本次作业较为简单,方法的循环使用也较少,所以代码复杂度较低,内部的耦合性很低
3、类图
可知类间关系很简单,代码架构十分清晰,主要由Elevator类去处理请求并输出。
第二次作业:
1、设计策略
第二次作业,为了更好地处理请求队列,增加了一个Work线程作为读入线程,Elevator类保留,即读与处理同时进行,从而更好地处理主请求和捎带。
MainClass类仅作为两个线程的启动器:
1 public class MainClass { 2 public static void main(String[] args) { 3 ArrayList<PersonRequest> list = new ArrayList<>(); 4 Work work = new Work(list); 5 Elevator elevator = new Elevator(list); 6 work.start(); 7 elevator.start(); 8 } 9 }
请求队列作为输入类和处理类共享队列,输入负责add,处理在达到读取请求后,并将其放入电梯后,remove。
这次主要变动的是Elevator类为了处理主请求,新增了inList列表用于储存已进入电梯但未到达目标楼层的请求,主请求则为inList.get(0),即最先到达电梯的请求。
1 private int getInLoop(int i) { 2 int j = i; 3 TimableOutput.println("IN-" + list.get(j).getPersonId() 4 + "-" + curFloor); 5 inlist.add(list.get(j)); 6 list.remove(j); 7 j--; 8 return j; 9 }
当请求进入电梯后,将其从总请求队列List里删除,加到inList中,然后在inElevator方法中循环判断,满足条件则进入电梯。条件中不仅要满足发出请求楼层为当前楼层,还要满足该请求和电梯内主请求是否满足捎带原则(如果电梯内无人,则直接进入就ok),代码如下:
1 private void carryAno() { 2 int i; 3 if (list.size() > 0) { 4 for (i = 0; i < list.size(); i++) { 5 if (list.get(i) != null) { 6 if (list.get(i).getFromFloor() == curFloor) { 7 if (inlist.isEmpty()) { 8 flag = 1; 9 inElevator(); 10 } else if (getIfcarry(i)) { 11 inElevator(); 12 } 13 } 14 } 15 } 16 } 17 }
有个需要注意的点就是,在笔者的架构下,电梯在开关门间需满足先下后上原则,这样主请求的判定才正确。
2、方法复杂度
这次的方法中changeFloor区分了出发楼层和目标楼层是否同正负的情况,如果异号,那么需要在循环时跳过0层,所以代码较为重复和冗长。inElevator和run方法中循环比较多,新增判断变量加入了不少,涉及较多边界条件的处理,因而复杂度较高。整体来看,方法复杂度适中。
3、类图
架构比较简单,work类读入与Elevator类处理同时进行。
第三次作业:
1、设计策略
本次为3部容量、运行速度、停靠楼层均不同的电梯同时运行,且存在中转情况,因而加入了调度器负责分析请求并下发给电梯请求以及Newre类用于重写PersonRequest类,用于需中转乘客在到达中转楼层的请求变更。
Newre类,携带原本的 PersonRequest,并新加from和to用于记录到达中转站后的请求变更:
1 public class Newre { 2 private PersonRequest request; 3 private int from; 4 private int to; 5 6 public Newre(PersonRequest request,int from,int to) { 7 this.request = request; 8 this.from = from; 9 this.to = to; 10 } 11 12 public int getFrom() { 13 return from; 14 } 15 16 public int getTo() { 17 return to; 18 } 19 20 public int getId() { 21 return request.getPersonId(); 22 } 23 24 public PersonRequest getRequest() { 25 return request; 26 } 27 28 public void setFrom(int from) { 29 this.from = from; 30 } 31 32 public void setTo(int to) { 33 this.to = to; 34 } 35 }
每当用户到达目的楼层时,判断其最初请求的目标楼层与Newre记录的to是否相同,不同则为中转乘客,并进行请求变更加入到总请求队列中等待分配:
1 if (inlist.get(i).getTo() != to) { 2 inlist.get(i).setFrom(curFloor); 3 inlist.get(i).setTo(to); 4 addRequest(inlist.get(i)); 5 }
中转乘客中转楼层的确定在调度器中,当请求的from和to不能由一个电梯完成时,遍历-3到20层确定中转楼层(初始携带电梯与中转后电梯须有公共楼层,且to在中转电梯的可停靠楼层中)
需要注意的点是程序结束的标志是总请求队列读取到null且3部电梯的请求队列均为空,且都关上了门(关门的事儿别问为什么知道的)
2、方法复杂度
较为复杂的方法均集中于调度器,说明调度器的方法需要进一步拆分优化,调度算法复杂度有点儿小高(还是太菜惹
3、类图
多加了一个调度器线程,用于实时监控总请求队列和分发请求,三部电梯利用Elevator类建3个就完事儿了
1 public class MainClass { 2 public static void main(String[] args) { 3 List<Newre> list = new ArrayList<>(); 4 5 List<Integer> astList = 6 Arrays.asList(-3,-2,-1,1,15,16,17,18,19,20); 7 List<Integer> bstList = 8 Arrays.asList(-2,-1,1,2,4,5,6,7,8,9,10,11,12,13,14,15); 9 List<Integer> cstList = 10 Arrays.asList(1,3,5,7,9,11,13,15); 11 Queue queue = new Queue(list); 12 Elevator elevator1 = new Elevator(6,400,'A',astList,list); 13 Elevator elevator2 = new Elevator(8,500,'B',bstList,list); 14 Elevator elevator3 = new Elevator(7,600,'C',cstList,list); 15 Distribute distribute = 16 new Distribute(elevator1,elevator2,elevator3,list); 17 queue.start(); 18 distribute.start(); 19 elevator1.start(); 20 elevator2.start(); 21 elevator3.start(); 22 } 23 }
二、bug分析及修复
互测时他人出的bug多是手残或者边界条件考虑不全,我的bug也是集中在边界条件的考虑上。
我是利用自己根据边界条件造几组数据进行测试的,比如程序结束的判断上,不能只关心队列是否已空,还要确保电梯门都关了(颓废
bug Hunter提醒您:边界条件不注意,强测挂一片
三、心得体会
这个单元的作业在时序上要求要高不少,生产者消费者模型、多线程蛮有趣的,比某多项式求导有意思多了(逃
希望能学习大佬第三次作业的架构和调度算法,让我的电梯智商再高点儿~