OO的多線程電梯做業已經結束了,回顧三次做業,我對多線程設計、編程和調試有了初步的認識與見解,在任務不斷加深的過程當中感覺到任務結構所存在的優點和問題。藉助此次的博客,如今從如下幾個方面進行總結:java
初次接觸多線程編程,我採用最基礎的生產者-消費者模型來實現,共享對象爲RequestList
,生產者線程(主線程)負責接受請求並向請求隊列投遞請求,而消費者線程(電梯)則採用對RequestList
的逐個取出指令並執行。算法
在第二次做業的結構中,採用了輸入(主)線程-調度線程-電梯線程的三線程結構,結構已有意識地爲多電梯運行和任務分拆創建結構,具體來講,有如下特色:編程
第三次做業需求的主要變化在於多任務調度、不一樣的電梯運行屬性和運載任務的分拆,爲此第三次的結構在第二次結構拓展而來,並主要作了以下的改進:安全
對於這三次做業,我從第二次做業纔開始認證考慮並實現架構,第一次做業基於最基本的生產者-消費者模式,類的定義和功能不多且相對固定,於是在此僅展現類圖,而將具體的OO度量放在做業二和做業三共用的架構上。數據結構
做業二和做業三所用的架構是一致的,做業三相較做業二,對於每部電梯除了其停靠樓層、速度和容量作了微調,其主要差別集中體如今主調度器中調度算法實質地填充(在做業二中主調度器是一個形式化的空殼)。多線程
經過觀察上述表格,複雜度太高的方法集中於兩個關鍵詞:調度和掃描;複雜度太高的類集中於電梯和任務列表的過耦合。架構
if-else if-else
的條件判斷,於是分支複雜度就很高。不過,在徹底基於先驗知識的狀況下,我認爲固定策略是一種較優的選擇,在這一點上方法複雜度太高不可避免。ev(G):Essentail Complexity,用來表示一個方法的結構化程度,範圍在\([1,v(G)]\)之間。併發
iv(G):Design Complexity,用來表示一個方法和他所調用的其餘方法的緊密程度,範圍在\([1,v(G)]\)之間。異步
v(G):循環複雜度,能夠理解爲窮盡程序流程每一條路徑所須要的試驗次數。函數
OCavg:表明類中方法的循環複雜度的平均值,具體來講是因爲條件分支和嵌套複雜狀況。
WMC:表明類的總循環複雜度,具體來講是方法之間互相調用複雜狀況。
根據上述數據,除去一些tool靜態工具類,本次做業各種之間的依賴性比較正常,於是側面反映出對各模塊功能的劃分仍是比較正確的(固然,不過二者做爲總體對外還不錯)elevator
和taskList
兩個類之間耦合是個意外
SRP-單一責任原則:從宏觀上看,各線程各司其職,功能劃分比較合理;從微觀上看,每部電梯與其對應的任務鏈表責任劃分不清晰,致使後期優化時實現複雜度和運行復雜度都比較高。
RequestPool
和Elevator
,兩個模塊在新功能加入後基本沒有改動。ISP-接口分離原則:符合,主要是由於在程序中實現的接口類方法並很少,只有主調度方法和樓層方向的工具類。電梯的運行算法採用了Look,因爲維護數據結構的特色,幾乎徹底內嵌很差變動運行策略。
在本章的做業中,個人程序在公測和互測階段均沒有被發現Bug,我認爲這主要取決對架構的重視、課下大量的自動測試和保守的synchronized。於是在本章中,我將着重討論本身在結構設計時如何避免產生bug的一些想法:
Dispatcher
,其數據的輸入接口有且僅有RequestPool
的get類方法,對於Elevator
,其數據的輸入接口僅有TaskList
的方法,這樣使基於notify
和wait
的守護者模式變得很安全(由於線程的守護對象是惟一的)。FloorTool
靜態方法類集中解決這些問題:public static int index2Floor(int index); public static int setDirectionDown(); public static int setDirectionUp(); public static int setDirectionStill(); public static boolean isDown(int dir); public static boolean isUp(int dir); public static boolean isStill(int dir); public static String getDirectionName(int dir); public static boolean isLegalFloorIndex(int floorIndex, int[] legalList); public static boolean isDirectTransport(int from, int to, int[] legalList); public static int getDirection(int from, int to);
@<Elevator A>:State -> Rest @<Elevator C>:State -> Rest @<Elevator B>:State -> Rest <Dispatcher>:Get a New Request '1-FROM-3-TO-1' <Dispatcher>:Task <1-FROM-3-TO-1> dispatched to C @<Elevator C>:State -> Recover <Elevator C>:A new Task '1-FROM-3-TO-1' Have Been Validated <Elevator C>:Direction Change: STILL -> UP <Elevator C>:Direction Change: UP -> STILL <Elevator C>:Direction Change: STILL -> DOWN <Dispatcher>:ID 1 Task Finished <Elevator C>:Direction Change: DOWN -> STILL @<Elevator C>:State -> Rest @<Client>:State -> Input Terminated <Elevator A>:Have Received Input Terminate Signal. <Elevator B>:Have Received Input Terminate Signal. @<Elevator A>:State -> Recover <Elevator C>:Have Received Input Terminate Signal. @<Elevator A>:State -> Normal ShutDown With 0 Tasks @<Dispatcher>:State -> Normal ShutDown @<Elevator B>:State -> Recover @<Elevator C>:State -> Recover @<Elevator B>:State -> Normal ShutDown With 0 Tasks @<Elevator C>:State -> Normal ShutDown With 1 Tasks @<Main>:State -> Normal ShutDown
本章做業中,模塊劃分好了,各個線程專人專事,在功能上就沒什麼大問題,但難點在於各個線程之間協同運行,我所遇到的最大問題就是異步線程之間的通信和同步問題,爲解決這個問題,我基本仍是使用了多線程編程中的生產-消費者及守護者模式,僅使用wait()
和notifyAll()
兩種線程狀態控制,這種模式及其思想大體出如今了程序架構的以下幾個方面:
我認爲在這種模式驅動下的線程其特色最重要的就是隱私性和主動性,線程之間因爲存在」托盤式「的設計,線程內部的隱私性都很高,共享對象不多,須要考慮的線程安全問題就要少不少;同時,藉助守護者模式,消費者線程主動地按需獲取和主動地進入結束狀態,這令外部線程對本線程運行狀態的影響和干預縮小到了局部而固定的代碼上,減小未知狀況的討論,固然,這也對線程自身運行的邏輯完備性提出了更高要求,不然若是出現問題其餘進程也一籌莫展。
在邏輯完備性的考慮上,我認爲最須要注意的即是多條件下的守護者模式對終止條件的判斷,進過屢次的嘗試,我逐漸地摸索出了在編碼實現的模板:
public synchronized getRequest(){ while(requestList.isEmpty()){ // 守護條件 if(inputTerminate && runningTask==0){ // 守護條件下的特例 return null; } wait(); } return requestList.remove(0); }
RequetPool
和輸入線程傳入的請求同等對待,要麼子任務的下達由電梯線程運行調度器的方法完成。wait()
時是守護的充要條件。固然,還有一種書寫思路,那就是直接將守護條件寫成充要條件,並在後續的操做中對特例進行判斷。while()
循環條件的變量固然算一個,可是在進入while後條件判斷分支也必須考慮,在模板中:requestList
,inputTerminate
,runningTask
三者對wait的執行都有影響,所以任何對while的條件判斷及其內部語句中的成員產生變化的,都須要加上對應的notifyAll()
語句。對其餘的線程通信方式嘗試較少:
上文已經提到,基於生產-消費模式的信息交互模式對線程自身邏輯的封閉性和完備性要求很高,但隨着多線程編程後續功能和狀況逐漸複雜,經過主動獲取托盤信息的方式可能顯得不可行。這時候可能就須要外部線程直接使用interrupt()
等手段讓程序陷入異常態進行處理,所以還須要對更多的線程協調和通信方式進行了解。
鎖優化:
本章做業並無涉及到實際業務狀況中高併發的狀況,於是並無促使我過多的考慮鎖優化,幾乎全部的共享對象都採用的是synchronized()
的方法。
電梯和其一對一對應的列表耦合重:
耦合性太高的問題隨着優化方法的嘗試和優秀同窗架構的分享而逐漸顯現,總結其緣由,主要仍是在於對電梯功能劃分時策略不夠好:原設計中將電梯自己和乘客的行爲分割並分別用Elevator
和TaskList
實現,但實際上後續當須要結合乘客分佈和電梯狀態進行預測時,二者因爲平級,於是耦合很大。
如今考慮,仍是應該將TaskList
做爲輔助,以Elevator
爲主進行運行和乘客的運動。
任務列表TaskList
結構過鬆散:
因爲實現Look算法,我實現了基於每一層樓的PickList
和PutList
以表示電梯須要在此層接放的乘客,這種方式確實能很好地實現Look算法,可是若是在改進時涉及到仿真預測、乘客選擇性拿放時,這種鬆散的數據結構就須要不少的輔助數據來維護,也就是方便於一次性寫入但繁瑣於後續修改。
目前的初步改進想法是,保留PickList
但去除PutList
,乘客的投放交由電梯內部一個小隊列處理。