關於目的選層電梯的設計與優化方案


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 }

  我的做法是創建了三個不同的電梯類,分別實現這個接口,這樣會造成類的數目過多且三個類之間的相似度特別高。其實可以創建一個電梯類並實現這個接口,然后把三個電梯之間不同的部分通過參數傳進來,實現一個類的三個不同對象即可,這一點是需要改進的地方。

  以上幾點只是個人拋磚引玉的愚見,一定有不周和不對之處,望讀者多多包涵並批評指正。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM