前言html
在繼上次表達式求導的三次做業後,我又完成了多線程電梯問題的三次做業,感官上,多線程的做業難度要弱於表達式求導,可是實際上,多線程電梯問題,由於考慮到線程間的通信,以及線程安全,實際上要困難一些。安全
設計策略數據結構
第一次做業——單個無調度策略的目的選層電梯:多線程
第一次的做業只是作一個沒有任何調度策略的目的選層電梯,所以在設計上並無動腦子(捂臉),採用單例模式建了一個名爲User的類用於存儲輸入的請求隊列,建了一個名爲Elevator的線程類和名爲Main的主函數類,輸入是在主函數裏完成的,採用的策略就是,在主函數中建立並啓動電梯線程,而後主函數不斷地往User單例裏面寫數據,電梯不斷獲取請求,就是一種很是傻瓜的寫法。架構
第二次做業——單個採用ALS(捎帶策略)的目的選層電梯:模塊化
此次做業我採用的策略是讓電梯一層層地前進,而且由電梯主動詢問調度器是否有用戶能夠稍帶,而在電梯地運行軌跡上我遵循樓道間通常電梯地運行方式,即當電梯上行地時候,不接受任何下行地請求,同理,下行不接受上行地請求,所以電梯將會在樓層間不斷地上下,理論上對於必定地用戶數量,這個調度方法擁有一個固定值。函數
爲了實現這個策略,我創建了用戶類和樓層類,兩個類之間用Arrarylist結構聯繫在一塊兒,在查詢的時候,順序爲該樓房第幾層,第幾個用戶的請求是什麼。在此基礎上,我在Scheduled類(調度器類)裏創建了兩個樓房隊列,一個表示上行,一個表示下行,同時我把Input線程從Main中摘了出來,Main只承擔初始化和啓動線程的工做。而在Evelator類裏,電梯有一個屬於本身的隊列,這個隊列對應樓層的每一層,存儲目標爲該層的用戶,同時,電梯的行爲僅有本身控制,也就是說Input和Scheduled沒法直接修改電梯的運行方式。電梯與Input之間經過Scheduled創建聯繫,Input獲取外界請求,Scheduled負責把數據分類處理,Evelator從Scheduled中請求數據,判斷線程狀態等。性能
整體上來講,我經過Scheduled將Input和Evelator基本上徹底分隔開,所以在實際操做的過程當中,我只保護了Scheduled的讀取和修改方法就實現了線程的保護。學習
第三次做業——多個採用ALS(捎帶策略)的帶有不一樣屬性的目的選層電梯測試
相比於第二次做業,此次做業加入了電梯的限制,包括電梯的停靠樓層限制、人數限制、不一樣的樓層運轉時間。可是本質上,和第二次做業並無大的差距,第三次做業,我重構了用戶類,第二次做業用戶類我採用了字符串存儲數據,顯然,這種數據模式對於使用類的人來講並不友好,所以我在User類的下一層加入了Contaner類,該類用於應對分層時可能出現的多條語句,剩下的架構基本和第二次保持一致,只作了以下的修改,Evelator類在第二次的基礎上添加了屬性初始化,Main函數增長了一部分初始化數據的生成,Scheduled類裏增長了須要換乘乘客的靜態處理方法。
在線程安全的處理上,由於增長了換乘乘客,所以在限定線程結束上修改了一部分,其餘並無變更。
總結:
我的感受此次做業,從第二次開始,架構就設計得比較好了,基本實現了總的設計思路,即電梯負責跑,輸入負責輸入,調度器負責數據處理,把類的功能基本上實現了獨立和分割,這使得我第三次做業較輕鬆地完成了。而在線程安全上,我使用的是synchronized,前後嘗試了鎖方法,鎖類,鎖類的一部分,鎖對象的一部分,最後爲了保險採用了鎖整個類的方法,不得不說這種鎖的確很低效。除此之外,除了第一次做業採用了單例模式,我後兩次做業均使用了傳遞參數的形式,我始終以爲單例模式不夠直觀。
基於度量分析代碼
ev(G)基本複雜度是用來衡量程序非結構化程度的,非結構成分下降了程序的質量,增長了代碼的維護難度,使程序難於理解。
Iv(G)模塊設計複雜度是用來衡量模塊斷定結構,即模塊和其餘模塊的調用關係。該數值越高,說明模塊的耦合度高,難以複用。
v(G)是用來衡量一個模塊斷定結構的複雜程度,數量上表現爲獨立路徑的條數,即合理的預防錯誤所需測試的最少路徑條數,圈複雜度大說明程序代碼可能質量低且難於測試和維護,經驗代表,程序的可能錯誤和高的圈複雜度有着很大關係。
注:上述引用自http://www.javashuo.com/article/p-hrljazti-hd.html
第一次做業 :
類圖:
度量分析:
自我點評:
從上圖,很清晰,個人第一次做業設計很是簡單,就是單純爲了完成做業在main裏,並且方法數量很是少,在evelator裏面甚至只有兩個方法,一個是爲了簡化sleep的函數,另外一個就是run,能夠說除了簡單明瞭以外,這個設計一無可取。
根據SOLID原則分析:
個人三次做業都不會涉及里氏替換原則和接口隔離原則,由於我壓根沒有使用繼承和接口。而僅就此次做業分析,開閉原則幾乎沒有遵循,若是我想在此次做業基礎上實現後幾回做業,那麼重構是必然結果,而單一功能更不可能,由於我只有三個類卻實現了電梯,調度器,還有輸入,顯然電梯等的功能,根據單一功能原則應該分離開。
第二次做業 :
類圖:
度量分析:
自我點評:
從度量分析圖來看,個人此次設計不考慮類,只看方法,方法的耦合度至關低,預防錯誤所須要的測試路數也很是少,只有少部分方法在維護的難度上存在問題,從數據來看,此次的設計至關成功。從UML類圖能夠清晰地看出個人調度器的類的結構,我把調度器,輸入,電梯,以及主函數的功能徹底地分開了,彼此之間實現了徹底的獨立功能。可是,此次的設計實際上侷限很大,此次做業我當時並無考慮擴展性,所以在Onewayticket這個類中,把多個用戶數據用字符串的形式結合起來,這種設計,理論上適應性很是高,但實際上,對代碼的維護和改變影響很惡劣,這是我當時沒有考慮的。
根據SOLID原則分析:
這次做業相比於第一次,在單一功能原則上實現了大的突破,雖然沒有達到標準的一個類一個功能,但至少,一個方法一個功能,實際上若是利用相似於宏的結構化,代碼的模塊化能夠進一步增長。關於開閉原則,User涉及的類實現的較爲糟糕,主要緣由在於字符串的拓展着實麻煩,可是對Evelator類的實現能夠說徹底符合開閉原則,具體將在第三次做業介紹。里氏替換原則和接口隔離原則,仍是沒有涉及相關內容。
第三次做業 :
類圖:
度量分析:
自我點評:
第三次做業其實是第二次做業改的,做業二改(做業三)繼承了做業二全部的優勢,而且在此基礎上,關於User的改進使得User得到了更好地拓展性,同時User的數據形式使數據的得到和分析更加簡潔。在設計之初實際上考慮過工廠模式,可是感受對於只有三個電梯沒有必要,實際上若是嘗試一下,代碼應該會簡潔不少。在度量分析裏,有個叫作gettranfer的方法,圈複雜度太高,實際上問題並非處在這個方法上,而是出在schedule這個系列的方法,這個方法的添加主要用於用戶的換乘,裏面寫的是很麻煩的換乘方式,這裏我採用的是靜態的邏輯,實際上,這種設計不管是性能,仍是實現都是下等,惟一的優勢就是不怎麼費腦子,應該採用帶權圖之類的東西作更好一點。
根據SOLID原則分析:
由於第三次做業又能夠叫作第二次做業改,因此這部分分析是同樣的。個人設計在開閉原則上實現的很是好,對於schedule類,不考慮重構底層數據結構形成的轉變,我只添加了一個用於用戶換乘的方法,而對於Evelator類,我只添加了一個初始化構造方法,能夠說,我認爲實現的已是很是好了。
自我BUG分析
本次電梯系列做業,我遇到的BUG種類很是少,三次做業有兩次進入互測沒有被HACK,還有一次由於重大邏輯錯誤,致使沒有進入互測。BUG分爲兩類,一類是線程安全及協助BUG,還有一類是邏輯BUG。
線程安全及協助BUG:
public boolean endthread() { synchronized (this) { if (waitforup == 0 && waitfordown == 0) { if (sign && waitfortranfers == 0) { return true; } try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } if (waitfortranfers == 0 && waitfordown == 0 && waitforup == 0 && sign) { return true; } } return false; }
這個方法位於第三次做業的Scheduled類,從第二次做業,全部的安全控制都在Scheduled類中,上面的代碼做用是判斷當前的電梯線程是否須要掛起或者結束,上面是最終的正確版本,這個BUG我改了兩次,一次是沒考慮中轉的用戶進入電梯後還有可能出來的問題,這使得用戶還沒出電梯,換乘電梯的線程就已經結束了;第二次是,改了第一個BUG後,電梯線程無法正確結束。
邏輯BUG
private User schedule(int id,int from,int to) { ArrayList<Container> temp0 = new ArrayList<Container>(); int fromele = whereyouare(from); int toele = whereyouare(to); int tranfers = 3; Boolean reverse = false; if ((fromele & toele) == 0) { tranfers = gettranfers(from,to,fromele | toele); if (from < tranfers && to < tranfers) { if (from > to) { tranfers = 3; } reverse = true; } Container container = new Container(tranfers,ele(fromele)); temp0.add(container); Container container1 = new Container(to,ele(toele)); temp0.add(container1); waitfortranfers += 1; } else { Container container = new Container(to,ele(fromele & toele)); temp0.add(container); } User temp = new User(id,temp0,reverse); return temp; }
這部分代碼只是從Scheduled類裏的schedule系列方法的一部分,這裏出現了一個重大的邏輯BUG。我採用的是二進制編碼來判斷人員的出入電梯和換乘思路,而後我從邏輯上忽略了一種可能,這使得我再過了中測了,強測直接GG,只得了4分。
如何發現別人的BUG
實際上,這部分我並無合理地策略,我沒有像那些大佬同樣搞了自動評測機,並且由於忙於OS和本身的私事,實際上並無多久的時間搞互測,互測採用的仍是之前的老思路,先拿出本身的測試集,而後再編寫一些邏輯上可能出錯的測試集,測試邏輯和線程安全問題。因爲此次時間不足,我甚至沒有細看代碼,針對性地編寫測試集。下一次做業能夠考慮編寫一個自動評測機來實現自動化評測了。
心得體會
這三次做業我的感受,操做簡單,理論複雜,設計苦難。多線程問題最突出的問題是線程安全問題。對於這個問題我體會良多。第一次做業,本質上是用多線程完成單線程任務,這裏線程安全問題並不明顯;第二次做業,線程之間共享資源的互斥是個巨大地問題,我從這裏纔開始學習synchronized的用法,我前後嘗試了在不一樣的類裏面鎖相同的對象,可是,這種模式會使得檢查安全問題的時候很是混亂,幾乎不具備邏輯性,因此後來我把全部的鎖都放到了方法裏。最初的設計,爲了減小鎖形成的性能損失,所以我儘可能鎖了對象,並且鎖的對象都很是細,這在結束線程的判斷上出現了語法問題,由於結束線程理論上須要獲取一整個完整對象的鎖,結果鎖的對象不一致,形成了矛盾,實際上這個問題,我以爲能夠用一個鎖的對象的記錄解決,可是當時爲了方便和安全,改爲了鎖一整個對象,這部分仍然能夠獲得改進。
其次,大概就是設計原則上的問題了。我認爲一個類的全部行爲應該都算它的方法,並且,對於這系列做業,並無其餘的類具備相同的電梯的功能,所以我並無徹底按照單一功能原則實現,但對於大的類,類的功能是徹底不重疊的,而對於類的內部,針對這一系列做業,採用了低耦合的作法,並且遵循了開閉原則,這使得我第三做業的完成至關輕鬆,基本上就是copy。
再次,關於程序正確性的檢測,此次我感慨良多。第三次做業得益於第二次做業的完備設計輕鬆地完成,這使得我並無針對第三次做業進行覆蓋性測試,只進行了部分邏輯的弱測和推演,結果致使出現重大邏輯失誤,使得強測幾乎沒有分數,而實際上的改正只用了6個字符,這使我在難過之餘也開始正視覆蓋性測試的必要性,還有,一我的最大的敵人就是懶惰和傲慢,只有謙虛才能前行。