BUAA OO Unit2 電梯調度

此次做業完成了一個開環可選層電梯調度系統。第二次迭代加入了容量限制、多部電梯,第三次迭代加入了電梯樓層分工、增添電梯請求。java

1. 系統架構

graph LR MainClass--Requests-->Schedule Executor--Notify-->Schedule Schedule--Update-->Executor MainClass--Create-->Elevators Schedule--Check-->Elevators Executor--Operate-->Elevators Schedule--Adapt-->Method
  • MainClass用於對各個子系統的組裝,發送請求至Schedule
  • Schedule用於接收來自MainClass、Executor的信息,更新狀態
  • Executor監聽Schedule的改變,使用單線程操縱全部電梯,同時將操做結果返回Schedule。
  • Method爲策略模塊,實現同步控制與策略模塊的分離,能夠用於適配不一樣的策略。

2. 同步控制

定時控制

同步控制的主要由Schedule設定,由Executor執行。併發控制的核心爲一個阻塞定時監聽器,可實現可調整的定時控制。這個模塊的實現方法參考了java.utils.Timer算法

private long scheduledTime = Long.MAX_VALUE;

public synchronized void retrieveNextAction() throws InterruptedException {
    long curTime = System.currentTimeMillis();
    while (curTime >= scheduledTime) {
        wait(scheduledTime - curTime);
        curTime = System.currentTimeMillis();
    }
    scheduledTime = Long.MAX_VALUE;
    return;
}
  • scheduledTime變量存儲下一次計劃動做的時間,規定該時間只減不增。即只保障不存在早於scheduledTime的動做。緩存

  • retrieveNextAction爲阻塞方法,用於Executor阻塞獲取下一次的動做。安全

  • 存在新的預期執行的動做時,更新scheduledTime,調用notifyAll(),retrieveNextAction重置等待時間/取消等待。多線程

  • 沒有設定動做時序隊列,每次完成時,須要檢索所有可能的動做,並根據執行狀況設置下一個scheduledTime架構

  • 這樣作的主要優點在於沒有用到Thread.sleep方法,能夠實現任意時刻對請求的及時響應。併發

另外,除Schedule外,另外一個共享變量爲Elevator。這裏Elevator類僅用做記錄電梯狀態,提供改變電梯狀態的接口。全部操做由Executor根據Schedule產生,並在敏感操做(如關門-移動序列)的執行過程當中進行了上鎖,屏蔽Schedule類的全部請求,保證一致性。機器學習

對於電梯移動,在Executor中對Schedule上鎖保證安全性。對於人員流動,使用ConcurrentLinkedList定義可執行隊列保障安全性,並在電梯移動時清空。Executor請求完成後調用相應方法通知Schedule,根據策略更新預期動做。函數

UML協做圖 (Sequence Diagram)

elevator seqUml

* 協做圖中動做可能不知足正確性,但反應了動做之間的時序關係。性能

* 2個線程爲MainClass主線程和Executor執行線程。Schedule、Elevator爲共享變量,被動進行數據調取。

* 還存在Schedule-Elevator之間的調用。

同步控制相關版本迭代

  • 第二次做業:
    • 去除了沒必要要的分支,簡化了定時阻塞器、終止判斷的執行流程,取消了沒必要要的PersonAction包裝。
    • Schedule類中增長了notifyPersonCheckedIn,notifyPersonCheckedOut方法,以適應電梯容量受限致使的策略變化。
    • Schedule類內中的電梯相關數據由單一數據轉變爲列表控制。訪問數據時使用電梯索引值訪問。
    • Executor類內,每次檢測到Schedule類存在新動做時,由檢查單一電梯轉變爲檢查所有電梯。實現宏觀上的電梯併發效果
  • 第三次做業:
    • Schedule類中增長評判:人員是否須要從電梯中出去。前兩次做業中只要知足與人員請求目標樓層相等都要出去。
    • Schedule類中增長方法:AddElevator。在所有數據列表中增長一項便可。
    • 優化代碼結構,Schedule類使用泛型訪問電梯接口,實現不一樣種類電梯和對應策略類的匹配。

3. 架構設計

本節以第三次做業的版本爲準討論了架構設計和可擴展性。

Unit2 Task3 Interface Uml

須要實現2個接口:IElevator、OperationMethod。

  • OperationMethod須要對canGetIn、canGetOff、nextfloor詢問作出答覆,接收addElevator、 notifyRequestReceived兩個輔助更新請求。
  • IElevator需實現運行時間查詢、電梯狀況查詢、電梯操做、人員操做相關方法。
  • 實際上Elevator類已經實現IElevator,一些特殊種類的電梯能夠進行重寫經過相關方法實現。同時Elevator的類型能夠藉助泛型經過Schedule傳遞到OperationMethod,實現策略類與電梯的一一對應。

第三次做業經過繼承的方法已經實現了樓層限制電梯,以及針對3中樓層限制電梯的方法不一樣方法。

功能-性能平衡

因爲採用了策略與調度分離的架構,使得在實現相應功能的同時,爲策略的制訂保留了很大的空間,同時容許對不一樣的策略進行試驗。但在策略制訂的過程當中,可擴展性作的不是很好,功能的調整每每須要策略作出較大的調整,在前幾回做業中均進行了策略重構,可能也與使用的策略具備較高的特殊性有關。

準則異常

單一責任準則:Schedule類和Executor類是全部請求實現的關鍵路徑,對進一步添加請求不理想,應考慮創建請求接口,將請求執行步驟抽象爲【電梯操做-調度更新-時間等待-新的請求】。

接口隔離:應考慮將策略接口拆分紅髮送策略接口、接受數據改變接口。

需求擴展方法

對於後續的擴展,能夠根據擴展的類型進行版本迭代。

  • 級別1:具備不一樣樓層限制規則的電梯、更新評價方法。

    擴展ElevatorRestricted.Type枚舉類型,編寫新的MethodTypeD,調整MethodTask3分配策略。

  • 級別2:新的限制規則電梯/樓梯、不定時電梯。

    創建新的Elevator類,重寫Elevator相關方法,較大規模的調整MethodTask3分配策略,編寫新的MethodTypeD。

  • 級別3:新的請求類型。如:停用電梯、更改請求

    在Schedule類中增添新的方法適配請求,檢查Executor類的執行過程是否符合新條件下的請求要求。考慮IElevator、OperationMethod接口中是否須要增添方法適配請求。

    若是請求的類型較多,能夠考慮創建請求接口,將請求執行步驟抽象爲【電梯操做-調度更新-時間等待-新的請求】過程,在Executor和Schedule中設立相應設施。

  • 級別4:新的調度維度。如:水平電梯

    創建新的調度策略。重構電梯類,將一維操做改成2維,並在全部訪問位置進行修改。

4. 度量分析

類複雜度(Weighed Method Complexity)

第一次做業 第二次做業 第三次做業
wmc1 wmc3

第一次做業中對2種策略進行了測試,最終選用了略微優化的Als策略。另外一種策略過於複雜並且效果不是很好。第二次做業中對ALS策略進行了調整,以適配多電梯,效果相似於LOOK策略,但實現後致使MethodAlsMultiple複雜度太高。第三次做業將策略分紅主策略和子策略,類複雜度能夠接受,MethodAlsMultiple基本未做修改。

方法複雜度(Cyclomatic Complexity)

第一次做業 第二次做業 第三次做業
vg1 vg2 vG3

這幾回做業設計中Executor線程須要完成所有動做的發送,工做量較大,一開始放在了run方法,儘管後來作出了一些步驟的提取,仍舊有不少步驟被留在了run方法中,致使複雜度較高,這個是不恰當的。

另外,在第三次做業中,部分優化方法直接進行了重複邏輯的複製粘貼,在一個方法內引用了較多外部參數,還須要進一步重構。

循環依賴

第一次做業 第二次做業 第三次做業
dep1 dep2 dep3

第一次做業因爲分包不太恰當,致使根目錄和子包出現了循環依賴,同時策略類和調度器也存在循環應用。第二次做業調整了Timer類的分包,創建了策略類和調度器的共有引用類,解決了這個問題。第三次做業因爲子類實現了主類的內部接口,而主類有又持有子類的引用致使循環引用(因此根據規範應該把內部接口放在外部,或者編寫額外的工廠類組裝主類、子類)。

類結構圖

  • 第一次做業:實驗了2個方法:ALS和動態規劃。PersonAction抽象的不太恰當。Timer類分包不太恰當。

uml1

  • 第二次做業:PersonRequest嵌套提供的PersonRequest,用於實現一些特殊/虛擬的的請求。層級關係較第一次做業更好。但Elevator有待進一步抽象。

uml2

  • 使用接口-繼承的方式進行了迭代更新。層次結構進一步優化。不過MethodTask3和SubMethod關係有待調整(以前已經闡述)。MainClass引用過多,應創建初始化相關類。

uml3

5. BUG分析

第一次做業中,中測階段的bug主要爲題目理解不當,沒有正確的使用輸出包,強測互測沒有發現Bug。

第二次做業中,強測互測階段也沒有發現Bug。

第三次做業中,強測階段沒有發現bug,但互測階段發現了兩個較爲嚴重的bug。一個是電梯容量定義錯誤,然而在以前的中測強測中因爲數據較爲均勻、稀疏,加上C電梯利用率較少沒有被測出來,自測時也沒有再作檢查。另外一個是上電梯後,儘管已經通知了調度器更新,但在調度器更新函數的編寫時沒有考慮換乘問題,致使換乘時可能出現一我的上兩個電梯的狀況,在更新函數中加入一個刪除相同請求就能夠了。這個可能緣由是前兩次做業調度器編寫時內聯邏輯過多致使魯棒性不高,環境改變後忘記了相關內聯邏輯。

6. 測試

簡易評測樣例定時讀入類

使用本身編寫的ScannerX實現了一個簡易的定時輸入,知足基本的測試需求。在test模塊中,將main函數中的ElevatorInput替換爲ScannerX便可測試。定時輸入使用sleep進行定時,在第一次調用時初始化,根據當前時間進行修正。

測試策略

  1. 手動數據:構造少許邊界性數據
  2. 隨機測試:生成一些數據,在時間邊界投放。做業3中隨機測試偏向於高頻生成邊緣樓層(如3樓、-3樓、15+樓等)。
  3. 覆蓋性測試:覆蓋做業3中全部的換乘狀況

實際上第二第三次互測中均沒有發現其餘人的bug,多是強測成績比較好分到的屋大佬較多...第一次互測中因爲定時輸入沒有作時間修正致使hack數據時間不許確,沒有hack上。

和第一單元測試相比,此次測試評測機搭建更爲麻煩一些,不像第一單元直接使用sympy計算就能夠驗證正確性,同時樣例投放也更加困難一些。這致使測試的效率降低了一些。

7. 心得體會

多線程程序

此次是第一次完整地編寫多線程協同程序。在併發程序的編寫中,須要仔細地考慮衝突問題,但若是對於每個語句都進行考慮顯然是不划算的。一方面是經典模型引入,例如讀者、寫者問題,生產者、消費者問題的解決方法,一方面是系統架構的獨立性。若是將一個大的併發問題拆解成幾個獨立的鏈條,僅需考慮鏈條與鏈條的併發、鏈條內部的互訪問,即可以「分而治之」,運用相關模型逐個擊破。

優化策略

在第一次做業中一開始嘗試使用動態規劃的思想進行優化,然而實際上效果並非很理想,並且複雜度很高,後來採用了改進的ALS策略。實際上和緩存機制相似,因爲咱們並不知道將來可能存在什麼請求,所以沒法將其轉化成揹包問題,在請求來臨前就實現最優調度。ALS和LOOK等方法看似和動態規劃相比不是最優,但和SJF、FIFO等算法相似,都是一種基於已知內容的可行優化策略。

在第二次第三次優化的過程當中,基礎上採用了ALS算法的定向捎帶原則,並借鑑粒子羣優化算法的思想,對空閒電梯制定了打散策略,空閒電梯運動方向受到來自於其餘電梯,與電梯容量、距離相關的壓力的影響,使電梯容量傾向於在電梯運行空間內均勻分佈,以減小調度時間指望。同時第三次做業不一樣種類的電梯制定了單獨的策略,一個重要的策略是減小A類電梯空轉時間。最終在強測中均得到了99分以上的成績。

這兩個方法還有很大的改進空間。這幾回做業優化的主要路線都集中於如何應對將來可能的請求,而對當前已有的請求策略並非最優。第三次做業的請求分配也存在問題,可能與當前最優偏離較大,但在隨機數據狀況下偏離不太明顯。另外一個優化路線是何澤欣同窗的基於模擬的估價函數,模擬不一樣狀態下電梯運行時間以完成評判。若是能夠創建一個歷史與將來的請求相結合的分配方法可能會有更好的效果。

實際上優化一個策略函數是一個比較複雜的問題,特別是針對將來的請求指望。或許能夠有一些機器學習的方法,例如梯度降低法加以解決,完成對策略函數的擬合。

P.S.若是針對強測,考慮到助教、同窗之間的博弈,均衡應該是徹底隨機的數據,這個也是打散算法較爲重要的緣由。

相關文章
相關標籤/搜索