(1)從多線程的協同和同步控制方面,分析和總結本身三次做業來的設計策略及其變化。java
第5次做業:多線程電梯算法
基本照搬了課件上「生產者-消費者」模型的設計策略,將InputHandler設計爲生產者線程,將Scheduler設計爲消費者線程,將RequestQueue設計爲托盤。生產者與消費者的工做併發,提升效率。同時,每部電梯設計爲一個線程,由於每部電梯的運行彼此不干擾。InputHandler, Scheduler由主線程建立,三部電梯由Scheduler負責建立,這樣使得調度器能夠獲取電梯的狀態。但這種策略並非最優的。編程
第6次做業:文件監控緩存
這大概是寫得最失敗的一次做業。安全
按課件方法構建了線程安全的SafeFile類。實現了同步化的訪問文件狀態、寫文件方法。然而對於每一個record型請求單獨建一個文件寫入,違背了訓練初衷。多線程
每一個請求設計一個「監控器」線程,直接查看對應文件的狀態,與指導書的設計思路差別較大。這使得程序難以應對path-changed等大規模變化。併發
總的來講,此次多線程之間沒什麼協同可言。同步控制也僅限於對文件狀態的訪問。eclipse
第7次做業:出租車調度測試
此次做業的設計策略基本照搬第5次做業。輸入處理線程爲生產者,調度器線程爲消費者,請求隊列爲托盤。每輛出租車爲一個線程,依然由調度器建立。線程
寫了線程安全的SafeFile類用於輸出文本文件。乘客請求、參與搶單的出租車信息由調度器負責寫入,出租車運行狀況由出租車線程負責寫入,實現了線程協同。
(2)基於度量來分析本身的程序結構
1)OO程序代碼度量
第5次做業:
類名 | 屬性個數 | 方法個數 | 代碼規模 | main / run方法規模 | 點評 |
Elevate_5 | 0 | 1 | 27 | 17 | |
Elevator | 14 | 7 | 176 | 40 | 電梯控制邏輯複雜。 |
Floors | 2 | 4 | 24 | ||
InputHandler | 10 | 6 | 158 | 15 | 寫得太隨意,不少屬性更適合做爲局部變量。 |
Request | 4 | 59 | |||
-CarryRequest | 1 | 3 | |||
-FloorRequest | 1 | 3 | |||
RequestQueue | 6 | 4 | 56 | ||
Scheduler | 7 | 8 | 209 | 40 | 保留了上次的調度器代碼。 |
Tray | 3 | 4 | 76 |
第6次做業:
類名 | 屬性個數 | 方法個數 | 代碼規模 | main / run方法規模 | 點評 |
Detail | 4 | 3 | 64 | ||
FileState | 4 | 3 | 26 | ||
InputHandler | 9 | 6 | 107 | 類似的問題 | |
Monitor | 8 | 2 | 60 | 30 | |
SafeFile | 2 | 15 | 110 | 爲測試者寫了較多的方法 | |
Summary | 4 | 3 | 64 | ||
TestDrive | 0 | 1 | 15 | ||
TestThread | |||||
Trigger | 3 | 8 | 94 | 設計有問題 |
第7次做業:
類名 | 屬性個數 | 方法個數 | 代碼規模 | main / run方法規模 | 點評 |
_Point | 2 | 7 | 40 | ||
CHandler | 9 | 4 | 76 | 32 | |
CityMap | 4 | 4 | 114 | 算法代碼比較長 | |
FHandler | 2 | 4 | 55 | ||
Queue | 3 | 3 | 49 | ||
Request | 3 | 3 | 24 | ||
SafeFile | 2 | 5 | 57 | ||
Schedule | 6 | 6 | 146 | 44 | 調度方法夾雜輸出代碼 |
Taxi | 16 | 9 | 204 | 40 | 運行方法夾雜大量輸出代碼 |
TestDrive | 0 | 1 | 18 | 12 |
3)各次做業的類圖
第5次做業:因爲指導書給出了上次電梯的參考類圖,本身的設計基本遵循了指導書。可是Tray這個類沒能發揮出應有的做用,只是單純地緩存請求。更好的作法是同時緩存電梯狀態,電梯運行時更新,調度器須要時讀取。
第6次做業:寫得很糟糕,有「麪條代碼」的嫌疑。對Monitor和Trigger兩個類的職責沒有明確,並且Summary和Detail的代碼有大量重用。
第7次做業:不少地方參考借鑑了第5次做業(多線程電梯)。各個類的職責相對清晰。
4)UML的協做圖(從上到下依次是第5,6,7次做業的圖)
5)設計原則檢查(僅針對第7次做業)
DIP (Dependency Inversion Principle):高層次不依賴低層次。
程序有兩個渠道獲取輸入,一是經過文本文件獲取城市地圖,二是經過控制檯獲取叫車請求。個人程序沒有將這兩種途徑進行抽象和概括,致使程序對輸入方式變化的適應性很差。
命名:
在爲類、實例變量命名時我儘量遵循「顧名思義」,可是走向了另外一個極端。我將一個記錄點信息的類命名爲「Point」,結果後來發現有java.awt.Point這個類,使用GUI時很不方便。無奈之下我將本身寫的類更名爲「_Point」,花了一部分時間更名字。在顧名思義的同時,也儘可能不要取過於簡單、大衆化的名字,不然容易與JAVA類庫重名,形成麻煩。
另外,eclipse建議包名首字母小寫,類名首字母大寫,做爲初學者仍是遵照得好。
(3)分析本身程序的bug
第5次做業:
全部的「正確請求測試多線程功能」樣例均有錯(公測未發現或是被發現bug)。
問題所在的類是Scheduler。
被發現了一個CRASH是因爲想向文件寫入結果,但文件已經關閉致使的。這暴露出我對於多線程同步控制還沒有掌握,且本身構造的測試樣例太寬鬆,很容易露出破綻。
從設計結構角度分析,我把全部的電梯調度方法全寫入一個調度器類。Scheduler類的代碼規模更是破了200行,遠遠超過其它類。
在總結課上,我才瞭解到有更加均衡的實現方式。總調度器只負責將請求分配給不一樣的電梯,而每部電梯對應一個專門的調度器負責排請求的執行前後順序。這種方式顯然更合適,既是遵循均衡原則,又符合實際的應用場景。
第6次做業:
對方沒有進行公測,且沒有發現bug,故不做分析。
第7次做業:
被發現了一個bug,爲新增節點「等待狀態下出租車的運行不具有隨機性」。
問題所在的類爲Taxi,方法爲run_edge
這個bug被髮如今情理之中,由於我曾經嘗試過實現出租車的隨機運行,可是失敗了(出租車發生了瞬移)。最終改爲了一版肯定性運行的方法。
(4)分析本身發現別人程序bug所採用的策略
不少學生(包括我)在編寫多線程程序時遇到的一個技術難點就是如何讓程序正常結束(在eclipse下是在控制檯上方看到<terminated>)。儘管這不是指導書的硬性要求,可是可以正常結束的程序勢必帶來更好的用戶體驗和更強的魯棒性。若是被測者沒有在readme中對程序的結束進行說明,或是說明能結束但實際上沒實現的,則能夠報告bug。
邊界條件是在編程過程當中須要着重考慮的。一些在實際狀況中徹底有可能出現,但編程者很難考慮到的測試點上每每容易發現bug。第7次做業我經過構造「乘客請求起始座標正好是出租車所在位置」的樣例,成功發現了別人的bug。
後期的做業不但注重程序正確性,並且對程序設計原則也提出了要求。測試者能夠閱讀被測代碼,發現其中不符合設計要求的部分,予以扣分。
(5)心得體會
1)線程安全
在線程安全方面,重點是把握課上重點講過的「生產者-消費者」模型,學會將共享對象的存取方法設置爲同步化的,以及方法內部具體的寫法。拿到指導書以後,在分析程序須要哪些對象、對象之間關係時,必須額外考慮哪些對象須要被共享的問題。不管是本身寫被共享類的代碼,仍是包裝爲線程安全類,都要想清楚哪些方法須要保證原子性,並使用synchronized關鍵字。
2)設計原則
從第7講開始,課後做業將程序設計原則也歸入了做業要求和互評範圍。設計原則與程序的正確性沒有直接的關係,而是爲了讓程序具備較好的可讀性、可擴展性。在軟件開發行業,用戶需求發生變動是常常遇到的事件。遵循課上講過的一些設計原則能夠加強程序的可擴展性。在需求變動時,只須要對原有程序進行有限的修改,而不是推倒重來。本身在3次電梯做業中,後面的做業每每不能有效利用前面做業的代碼,緣由在於本身沒有在一開始構造一個良好的設計,沒有遵循設計原則。另外一些原則是爲了程序可讀性,由於未來咱們開發的程序只是一個大項目的一部分。不但要保證別人放心調用,並且最好在代碼中清楚寫出本身的邏輯。可能通過後面幾回做業的訓練,我可以更深刻地理解設計原則。