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.既然動態分配中間停靠樓層不好實現,就把一個電梯不能完成的所有請求枚舉出來,固定他們的中間停靠樓層。我的具體實現如下表:
|
不能簡簡單單直接將請求拆成兩部分放在請求列表里,這樣可能會有后半段先執行的邏輯錯誤,我的方法是隊請求做出特殊的標記
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 }
我的做法是創建了三個不同的電梯類,分別實現這個接口,這樣會造成類的數目過多且三個類之間的相似度特別高。其實可以創建一個電梯類並實現這個接口,然后把三個電梯之間不同的部分通過參數傳進來,實現一個類的三個不同對象即可,這一點是需要改進的地方。
以上幾點只是個人拋磚引玉的愚見,一定有不周和不對之處,望讀者多多包涵並批評指正。