- 本次做業須要模擬一個多線程實時多電梯系統,從標準輸入中輸入請求信息,程序進行接收和處理,模擬電梯運行,將必要的運行信息經過輸出接口進行輸出。
- 本次做業電梯系統具備的功能爲:上下行,開關門。本次多部電梯的可停靠樓層,運行時間,最大載客量都不相同。
- 電梯系統能夠採用任意的調度策略,即上行仍是下行,是否在某層開關門,均可自定義,只要保證在系統限制時間內將全部的乘客送至目的地便可。
- 電梯系統在某一層開關門時間內能夠上下乘客,開關門的邊界時間均可以上下乘客。
此次做業有幾個關鍵點:實時系統,正確調度,多線程交互。這就要求在設計中須要提早定義好運行邏輯,並且經歷了第一單元的歷練,在第一次做業就須要對後續做業可能出現的狀況進行設計——即儘可能最大化程序的可拓展性,下降程序模塊之間的耦合程度,這樣在需求變化時就能儘可能少的修改代碼達到需求。<br>本次做業三個階段性安排爲:java
本單元做業輸入輸出接口均已給出,所以重點就在於程序進程交互邏輯以及任務分配執行的算法。python
由於有了第一單元設計的經驗,爲了減小每次的修改量,在每次做業的實現中都儘可能採用邏輯分離式設計,每一個模塊獨立的從隊列中取出信息、處理信息、放置信息(反饋)。模塊之間只經過隊列交互,而後獨立的實現一套邏輯。針對各個部分,設計變化以下:git
addElevator(Elevator ele)
函數添加一部電梯使得電梯可被調度。針對樓層需求,我設置了有序列表使得樓層之間的變化保持連續性:github
List list = Arrays.asList(-3,-2,-1, 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20);
這樣在程序中修改樓層因此便可實現樓層之間的連續變化。算法
針對捎帶需求,在個人設計中將可捎帶斷定與捎帶執行分配到了電梯本身的線程中,所以這部分基本只對Elevator類進行了修改,在電梯開門、關門、變化樓層、任務開始時遍歷Mission隊列決定可捎帶的任務並判斷下一個停靠的目標樓層,以適應不斷到來的任務變化。設計模式
須要注意的是,此次由於捎帶的發生,須要進行Mission狀態的記錄——即人在電梯內或者電梯外->進一步爲了簡化邏輯,將這個狀態的判斷轉化爲「Mission在執行隊列或等待隊列中」。這樣作的好處是能夠將上下電梯統一塊兒來,基於上下電梯須要的電梯行爲表現(到達-開門-關門)是統一的這點,可使得電梯邏輯變得簡單,至於上或下只須要體如今輸出便可。安全
String determine(Mission request)
函數 /** * 考慮:電梯運行狀態+電梯選項+目標電梯 * 輸入Mission:目標換乘樓層(須要換乘時) * 1. 若是選擇只有一個,直接上 * 2. 若是選擇有多個,基於「換乘點是一致的」的條件 * 分支樹斷定(方向、捎帶、遠近、人多少) * @param request * @return 目標電梯名 */ private String determine(Mission request)另外,由於不一樣的電梯線程都須要訪問調度器的部分方法,將Scheduler做爲了Main類中的靜態實例處理,不知道算不算單例的一種實現,但由於Scheduler中保證了線程安全,並且只在主線程中實例化,所以這個靜態實例變量能夠正確使用
this.availableFloor = new ArrayList<>(); this.availableFloor.addAll(floors);來斷定可停靠的樓層,至於具體是哪些,在建立電梯時指定,電梯中還增長了相應的接口進行樓層判斷。實現整套邏輯。
public Boolean canDeliver(int floor) { if (!this.availableFloor.contains(floor)) { return false; } return true; }相應的在Scheduler中提供了函數判斷是否可直達
/** * 可直達? |--yes--> 使用直達電梯 * |--no---> 拆分紅兩階段 * * 當前階段性任務有多部電梯可完成:1.當前方向上可捎帶? ---> 2.電梯中人數較少? */這樣就可以較爲合理地規劃電梯的任務分配並處理好換乘狀況。
最終獲得的總體邏輯的時序圖以下:多線程
本次做業第一階段和第三階段都未發現bug,第二階段強測中發現了一個bug,主要是由於第二階段對調度性能要求較高,可捎帶的狀況必需要捎帶上才行,而我在設計Mission隊列以及Elevator正在執行的Mission存儲兩部分同步不是很好,致使Mission隊列中有任務,卻由於檢查不及時Elevator沒有檢查並執行這個任務,尤爲是某一層有不少人上電梯時(同時有不少人同一樓層的用戶請求到來)。這個bug的出現和輸入的時機有關,所以本身測試的時候沒有檢測到。<br> 第三階段我花費了很長時間在debug上,由於評測機一隻返回RUNNTIME_ERROR,但個人程序並不會出錯,爲此,我還實現了簡單地隨機測試腳本:架構
import random pair = [] l = [-3, -2, -1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] for i in l: for j in l: if i == j: continue else: pair.append((i,j)) n = 0 time = 0.0 with open("data.txt", 'a') as f: for t in random.sample(pair, 50): print("[{:.1f}]{}-FROM-{}-TO-{}".format(time, n, t[0], t[1])) status = f.write("[{:.1f}]{}-FROM-{}-TO-{}\n".format(time, n, t[0], t[1])) n += 1 if random.random() > 0.6: time += random.random() f.write("\n")
以上使用python自動生成數據,藉助Mistariano提供的黑箱接口能夠進行簡單地測試,數據生成能夠經過調節參數控制app
@echo off set num=0 :start set /a num+=1 echo ================No. %num% time======================= >> err.txt python gen.py | "C:\Program Files\Java\jdk-11.0.2\bin\java.exe" -cp out\production\oo_course_e_2019_16071064_homework_6;..\..\elevator-test-suit-0-3.jar Main >> err.txt 2>&1 echo ================ over ======================= >> err.txt goto start
雖然使用了大量的黑箱測試個人代碼卻依然有RUNTIME_ERROR的問題,因而我又嘗試其餘的debug,終於發現是由於程序退出時Scheduler發生了輪詢致使CPU時間超時(這一點評測機並無反饋,並且錯誤種類不該該是這個啊),這裏,JProfile的線程分析幫了我大忙
這個界面顯示了每一個線程實際運行的時間,若是有線程使用輪詢致使CPU時間過程能夠明顯的看出並直接定位。
相比較以前的設計,這個單元我認爲做爲比較好的是在第一次代碼就肯定了整個的設計框架,也證實在新需求到來時能夠較快的適應改動。
從圖中能夠看出各個類的功能劃分是比較清晰地,關於共享對象的設計以及線程類的實現是和預期設計一致的。並且每個線程類都不依靠其餘線程,是一個獨立的邏輯體,這大幅下降了模塊之間的耦合性,也使得代碼邏輯更爲清晰易懂。
代碼量分析
此次的代碼相比較第一單元有了很大的改觀,首先是類的代碼量,大部分功能類的代碼量分佈仍是比較均衡的,代碼量最多的類源碼沒有超過300行,並且在完成過程當中我也嘗試使用Javadoc風格的函數與類功能註釋,註釋比例有了很大的增長。重要的是,使用這樣的註釋以後在下一個階段須要更新代碼的時候可以很快的回憶起函數的功能與實現。<br>此次類間代碼量的均衡得益於功能上的分工明確,即將不一樣的功能實現交由不一樣的模塊完成,但其中由於電梯的運行策略最爲複雜,代碼行數較多。但總體上仍是比較合理的。
方法複雜度分析 此次代碼的複雜度分析呈現出如下結果,共出現了三個複雜的方法,一個複雜的條件判斷,一個長參數列表問題,這些問題的應該是能夠解決的。總體來看,代碼的循環複雜性(CC)爲2.49122807,其中循環複雜性最高的函數依舊是iterMission,高達13。這個函數的功能是遍歷Mission隊列並添加Mission到電梯的執行隊列中,從這一點來看,這個函數仍是有能夠簡化的空間的。
Class Name | Method Name | Code Smell |
---|---|---|
Elevator | Elevator | Long Parameter List |
Elevator | checkDirection | Complex Method |
Elevator | iterMission | Complex Method |
Scheduler | run | Complex Method |
Scheduler | determine | Complex Conditional |
Type Name | NOF | NOPF | NOM | NOPM | LOC | WMC |
---|---|---|---|---|---|---|
域數量 | public域數量 | 方法數量 | public方法數量 | 行數 | 類加權方法數 | |
Elevator | 8 | 0 | 15 | 4 | 302 | 58 |
Main | 1 | 0 | 3 | 3 | 37 | 4 |
Mission | 6 | 0 | 10 | 9 | 118 | 17 |
Scheduler | 5 | 0 | 11 | 7 | 200 | 33 |
Submission | 2 | 0 | 2 | 2 | 46 | 4 |
ElevatorState | 9 | 0 | 16 | 16 | 121 | 26 |
這是我第一次接觸多線程程序,以前只是在學習操做系統的時候瞭解過原理,但並無動手寫過多線程程序。經過此次實踐我不只掌握了多線程程序的設計方法,並且瞭解到設計模式在多線程交互中的關鍵性。不管是線程安全仍是死鎖預防,或者是線程之間wait/notify的等待喚醒機制,都讓我對線程交互有了更深層的理解,並且認識到使用JProfile分析程序運行狀態進行優化的必要性。<br>真正測試代碼時,白盒測試是比較直接的方式,但黑盒測試優點也能提供意想不到的效果與便捷性,並且從某種程度上講更能達到壓力測試的效果。<br>另外,在設計之初就考慮可擴展性是頗有必要的,一套好的架構必定是可以支持不斷擴展的架構,進行好架構設計對功能擴展不管是工做量仍是安全性都有很好的幫助。