• 关于目的选层电梯的设计与优化方案


    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 }

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

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

  • 相关阅读:
    改造我们的学习:有钱不会花,抱着金库抓瞎
    (转)我奋斗了18年才和你坐在一起喝咖啡
    初学者要知道的十件事
    [转]C#图像处理 (各种旋转、改变大小、柔化、锐化、雾化、底片、浮雕、黑白、滤镜效果)
    C#调用系统的复制、移动、删除文件对话框
    SQLite数据类型
    jquery禁用dropdownlist中某一项
    C# winform无标题窗体随意移动
    注册.NET Framework
    jQuery同步/异步调用后台方法
  • 原文地址:https://www.cnblogs.com/hyc2026/p/10764478.html
Copyright © 2020-2023  润新知