此次做業完成了一個開環可選層電梯調度系統。第二次迭代加入了容量限制、多部電梯,第三次迭代加入了電梯樓層分工、增添電梯請求。java
同步控制的主要由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,根據策略更新預期動做。函數
* 協做圖中動做可能不知足正確性,但反應了動做之間的時序關係。性能
* 2個線程爲MainClass主線程和Executor執行線程。Schedule、Elevator爲共享變量,被動進行數據調取。
* 還存在Schedule-Elevator之間的調用。
本節以第三次做業的版本爲準討論了架構設計和可擴展性。
須要實現2個接口:IElevator、OperationMethod。
第三次做業經過繼承的方法已經實現了樓層限制電梯,以及針對3中樓層限制電梯的方法不一樣方法。
因爲採用了策略與調度分離的架構,使得在實現相應功能的同時,爲策略的制訂保留了很大的空間,同時容許對不一樣的策略進行試驗。但在策略制訂的過程當中,可擴展性作的不是很好,功能的調整每每須要策略作出較大的調整,在前幾回做業中均進行了策略重構,可能也與使用的策略具備較高的特殊性有關。
單一責任準則:Schedule類和Executor類是全部請求實現的關鍵路徑,對進一步添加請求不理想,應考慮創建請求接口,將請求執行步驟抽象爲【電梯操做-調度更新-時間等待-新的請求】。
接口隔離:應考慮將策略接口拆分紅髮送策略接口、接受數據改變接口。
對於後續的擴展,能夠根據擴展的類型進行版本迭代。
級別1:具備不一樣樓層限制規則的電梯、更新評價方法。
擴展ElevatorRestricted.Type枚舉類型,編寫新的MethodTypeD,調整MethodTask3分配策略。
級別2:新的限制規則電梯/樓梯、不定時電梯。
創建新的Elevator類,重寫Elevator相關方法,較大規模的調整MethodTask3分配策略,編寫新的MethodTypeD。
級別3:新的請求類型。如:停用電梯、更改請求
在Schedule類中增添新的方法適配請求,檢查Executor類的執行過程是否符合新條件下的請求要求。考慮IElevator、OperationMethod接口中是否須要增添方法適配請求。
若是請求的類型較多,能夠考慮創建請求接口,將請求執行步驟抽象爲【電梯操做-調度更新-時間等待-新的請求】過程,在Executor和Schedule中設立相應設施。
級別4:新的調度維度。如:水平電梯
創建新的調度策略。重構電梯類,將一維操做改成2維,並在全部訪問位置進行修改。
第一次做業 | 第二次做業 | 第三次做業 |
---|---|---|
第一次做業中對2種策略進行了測試,最終選用了略微優化的Als策略。另外一種策略過於複雜並且效果不是很好。第二次做業中對ALS策略進行了調整,以適配多電梯,效果相似於LOOK策略,但實現後致使MethodAlsMultiple複雜度太高。第三次做業將策略分紅主策略和子策略,類複雜度能夠接受,MethodAlsMultiple基本未做修改。
第一次做業 | 第二次做業 | 第三次做業 |
---|---|---|
這幾回做業設計中Executor線程須要完成所有動做的發送,工做量較大,一開始放在了run方法,儘管後來作出了一些步驟的提取,仍舊有不少步驟被留在了run方法中,致使複雜度較高,這個是不恰當的。
另外,在第三次做業中,部分優化方法直接進行了重複邏輯的複製粘貼,在一個方法內引用了較多外部參數,還須要進一步重構。
第一次做業 | 第二次做業 | 第三次做業 |
---|---|---|
第一次做業因爲分包不太恰當,致使根目錄和子包出現了循環依賴,同時策略類和調度器也存在循環應用。第二次做業調整了Timer類的分包,創建了策略類和調度器的共有引用類,解決了這個問題。第三次做業因爲子類實現了主類的內部接口,而主類有又持有子類的引用致使循環引用(因此根據規範應該把內部接口放在外部,或者編寫額外的工廠類組裝主類、子類)。
第一次做業中,中測階段的bug主要爲題目理解不當,沒有正確的使用輸出包,強測互測沒有發現Bug。
第二次做業中,強測互測階段也沒有發現Bug。
第三次做業中,強測階段沒有發現bug,但互測階段發現了兩個較爲嚴重的bug。一個是電梯容量定義錯誤,然而在以前的中測強測中因爲數據較爲均勻、稀疏,加上C電梯利用率較少沒有被測出來,自測時也沒有再作檢查。另外一個是上電梯後,儘管已經通知了調度器更新,但在調度器更新函數的編寫時沒有考慮換乘問題,致使換乘時可能出現一我的上兩個電梯的狀況,在更新函數中加入一個刪除相同請求就能夠了。這個可能緣由是前兩次做業調度器編寫時內聯邏輯過多致使魯棒性不高,環境改變後忘記了相關內聯邏輯。
使用本身編寫的ScannerX實現了一個簡易的定時輸入,知足基本的測試需求。在test模塊中,將main函數中的ElevatorInput替換爲ScannerX便可測試。定時輸入使用sleep進行定時,在第一次調用時初始化,根據當前時間進行修正。
實際上第二第三次互測中均沒有發現其餘人的bug,多是強測成績比較好分到的屋大佬較多...第一次互測中因爲定時輸入沒有作時間修正致使hack數據時間不許確,沒有hack上。
和第一單元測試相比,此次測試評測機搭建更爲麻煩一些,不像第一單元直接使用sympy計算就能夠驗證正確性,同時樣例投放也更加困難一些。這致使測試的效率降低了一些。
此次是第一次完整地編寫多線程協同程序。在併發程序的編寫中,須要仔細地考慮衝突問題,但若是對於每個語句都進行考慮顯然是不划算的。一方面是經典模型引入,例如讀者、寫者問題,生產者、消費者問題的解決方法,一方面是系統架構的獨立性。若是將一個大的併發問題拆解成幾個獨立的鏈條,僅需考慮鏈條與鏈條的併發、鏈條內部的互訪問,即可以「分而治之」,運用相關模型逐個擊破。
在第一次做業中一開始嘗試使用動態規劃的思想進行優化,然而實際上效果並非很理想,並且複雜度很高,後來採用了改進的ALS策略。實際上和緩存機制相似,因爲咱們並不知道將來可能存在什麼請求,所以沒法將其轉化成揹包問題,在請求來臨前就實現最優調度。ALS和LOOK等方法看似和動態規劃相比不是最優,但和SJF、FIFO等算法相似,都是一種基於已知內容的可行優化策略。
在第二次第三次優化的過程當中,基礎上採用了ALS算法的定向捎帶原則,並借鑑粒子羣優化算法的思想,對空閒電梯制定了打散策略,空閒電梯運動方向受到來自於其餘電梯,與電梯容量、距離相關的壓力的影響,使電梯容量傾向於在電梯運行空間內均勻分佈,以減小調度時間指望。同時第三次做業不一樣種類的電梯制定了單獨的策略,一個重要的策略是減小A類電梯空轉時間。最終在強測中均得到了99分以上的成績。
這兩個方法還有很大的改進空間。這幾回做業優化的主要路線都集中於如何應對將來可能的請求,而對當前已有的請求策略並非最優。第三次做業的請求分配也存在問題,可能與當前最優偏離較大,但在隨機數據狀況下偏離不太明顯。另外一個優化路線是何澤欣同窗的基於模擬的估價函數,模擬不一樣狀態下電梯運行時間以完成評判。若是能夠創建一個歷史與將來的請求相結合的分配方法可能會有更好的效果。
實際上優化一個策略函數是一個比較複雜的問題,特別是針對將來的請求指望。或許能夠有一些機器學習的方法,例如梯度降低法加以解決,完成對策略函數的擬合。
P.S.若是針對強測,考慮到助教、同窗之間的博弈,均衡應該是徹底隨機的數據,這個也是打散算法較爲重要的緣由。