1.电梯采用LOOK算法,这是对SCAN算法的改进。假设现在电梯正在上行,到达某一层停靠后电梯轿厢里没有人并且当前楼层到顶层没有请求,则转向。这样就省去了从当前楼层到顶层,和从顶层回来的这一段折返的空载的路程(或者说载客可能性不大的的路程,因为不能保证转向后没有新的请求到来,但是综合考虑还是转向的运行时间期望更小,受益更大)。

  具体判断方法:

1 if (requests.isEmpty() && canPark()) {
2     if (!dispatcher.checkFurtherRequest(currentFloor, dir)) {
3         if (isOpen) {
4             closeDoor();
5        }
6        changeDir();
7     }
8 }

  其中checkFurtherRequest为:

 1 public synchronized boolean checkFurtherRequest(
 2     int currentFloor, ElevatorDirection dir) {
 3     if (dir == ElevatorDirection.UP) {
 4         for (int i = 0; i < requestQueue.size(); i++) {
 5             if (requestQueue.getFrom(i) > currentFloor) {
 6                 return true;
 7             }
 8         }
 9         return false;
10     } else {
11         for (int i = 0; i < requestQueue.size(); i++) {
12             if (requestQueue.getFrom(i) < currentFloor) {
13                 return true;
14             }
15         }
16         return false;
17     }
18 }

  其他函数根据名字可以大体推知其作用。

2.为了最大限度的不错过可以执行的请求,可以在关门前先遍历一遍请求列表,没有可执行的请求就sleep(200),之后再遍历一遍请求列表,若此时有可执行的请求就获取它并继续sleep(200)重复上述过程,若此时仍然没有可执行的请求就关门。虽然只有0.2s的差别,但是这样就可能让你不错过诸如下列的一系列请求:

  (虽然可能性极低,但实测确实有可能发生)

3.一开始我曾构思过写一个调度器线程,把请求动态分给三个电梯,如果有一个电梯不能完成的请求就动态分配中间停靠楼层,但这样需要线程之间频繁的通信,实现起来太过复杂,而且因为三个电梯的运行时间、限乘人数都不一样,要综合各种因素实现一个稳定的优化算法是极为困难的,且留给我们的时间也不允许。于是我的做法就是化繁为简,以不变应万变。找到了一个低付出高回报的方法。

  a.既然动态分配中间停靠楼层不好实现,就把一个电梯不能完成的所有请求枚举出来,固定他们的中间停靠楼层。我的具体实现如下表:

TO

FROM

-3

-2、-1

1

2

3

4~14

15

16~20

-3

直达

直达

直达

1-换乘

1-换乘

1-换乘

直达

直达

-2、-1

直达

直达

直达

直达

1-换乘

直达

直达

直达

1

直达

直达

直达

直达

直达

直达

直达

直达

2

1-换乘

直达

直达

直达

1-换乘

直达

直达

15-换乘

3

1-换乘

1-换乘

1-换乘

1-换乘

直达

奇数楼层直达

偶数楼层5-换乘

直达

15-换乘

4~14

1-换乘

直达

直达

直达

5-换乘

直达

直达

15-换乘

15

直达

直达

直达

直达

直达

直达

直达

直达

16~20

直达

直达

直达

15-换乘

15-换乘

15-换乘

直达

直达

  不能简简单单直接将请求拆成两部分放在请求列表里,这样可能会有后半段先执行的逻辑错误,我的方法是队请求做出特殊的标记

public class Request {
    private int id;
    private int from;
    private int to;
    private int destiny;
    private boolean in = false;
    private boolean out = false;
    
    public Request(int id, int from, int to) {
        this.id = id;
        this.from = from;
        this.to = to;
        destiny = 0;
    }
    
    public Request(int id, int from, int to, int destiny) {
        this.id = id;
        this.from = from;
        this.to = to;
        this.destiny = destiny;
    }
    //此处省略部分get和set方法
}

  设计了两种构造方法,单电梯可直达的用第一种构造方法,不能执行的用第二种构造方法,其中from不变,to为中转的楼层,destiny为最终的目的楼层。每次执行完一条请求后看看这个请求的destiny是否为0,若为0,该请求已经执行完成,删去;若不为0,则重新构造一个请求:

  request = new Request(formerRequest.get(i).getId(), currentFloor, formerRequest.get(i).getDestiny());

  以当前楼层为from,destiny为to,并立即插入请求列表中等待再一次被执行,这样可以保证逻辑的先后顺序不会出错。

  b.既然动态给三个电梯分配请求太困难,就让三个电梯来请求列表里“抢”请求,但这个“抢”是有约束的。电梯运行到某层后遍历请求列表,取得电梯自己可以执行的请求。什么是电梯自己可以执行的请求呢?就是currentFloor == requestFrom并且方向与电梯运行方向相同的请求。电梯每次到达某一层之后,先下人,然后再遍历请求列表来取得请求。

  这就是我们常说的Worker Thread模式,其类图如下:

 

  可以对比一下Worker Thread模式和普通的方法调用。

  在Worker Thread模式中,Client负责发送工作请求,它将工作内容封装为Request,然后传递给Channel,在普通的方法调用中,这部分相当于“设置参数并调用方法”。其中,“设置参数”与“创建Request”相对应,“调用方法”与“传递给Channel”相对应。

  Worker负责进行工作,它使用从Channel接受到的Request来执行实际的处理。在普通的方法调用中,这部分相当于“执行方法”。

  在进行普通的方法调用时,“调用方法”和“执行方法”是连续进行的。因为调用方法后,方法会立即执行,无法分开。但在Worker Thread模式中,方法调用和方法执行被特意被分开了。

  这种分离有什么意义呢?

  ①提高响应速度

  如果执行和调用不可分离,那么当执行需要花费很长时间时,就会拖调用处理的后腿。但是如果将调用和执行分离,那么即使执行需要花费很长时间也没有关系,因为执行完调用处理的一方可以先继续执行其他处理,这样就可以提高响应速度

  ②控制执行顺序,即调度

  如果调用和执行不可分离,那么在调用之后就必须开始执行。但如果将调用和执行分离,那么执行就可以不再受调用顺序的制约。我们可以通过设置Request的优先级,即控制Channel将Request传递给Worker的顺序来实现控制调用顺序,这就是请求调度(scheduling)。

  ③可以取消和反复执行

  将调用和执行分离后,还可以实现“即使调用了也不执行”和“即使调用了一次也可以反复执行”。

  ④实现分布式

  我们现在只是分开不同的线程来实现调用和执行的分离,但是利用这种思想,我们可以将负责调用的计算机和负责执行的计算机分开,让网络作为Channel来传递Request。

4.没有最好的优化,只有最好的架构,不能为了性能而破坏设计的架构,这样是得不偿失的,性能只是为你的架构锦上添花,不能为了性能而使架构变得不堪入目,本末倒置。对电梯类的建造我采用了抽象工厂模式生成了三个电梯类。其中抽象工厂为:

 1 public interface ElevatorFactory {
 2 
 3     static final int OPEN_TIME = 200;
 4     static final int CLOSE_TIME = 200;
 5     
 6     public abstract void runElevator();
 7     
 8     public abstract void walkIn(Request request);
 9     
10     public abstract void walkOut(Request request);
11     
12     public abstract void openDoor();
13     
14     public abstract void closeDoor();
15     
16     public abstract void moveOneFloor(int sign);
17     
18     public abstract int getSign();
19     
20     public abstract void changeDir();
21     
22     public abstract boolean canPark();
23 }

  我的做法是创建了三个不同的电梯类,分别实现这个接口,这样会造成类的数目过多且三个类之间的相似度特别高。其实可以创建一个电梯类并实现这个接口,然后把三个电梯之间不同的部分通过参数传进来,实现一个类的三个不同对象即可,这一点是需要改进的地方。

  以上几点只是个人抛砖引玉的愚见,一定有不周和不对之处,望读者多多包涵并批评指正。

posted on 2019-04-27 16:14  柠檬草Ian  阅读(1093)  评论(1编辑  收藏  举报