這三次做業,發現本身不管採起怎樣的架構,最後本身寫的類都只有兩個(除了給出的TestMain demo類)。java
在類圖中,都是隻有三小塊....架構複雜性徹底沒有變?算法
類圖:設計模式
第一次做業的架構是三次裏面最清晰的——生產者消費者模式。多線程
本身開始對於多線程徹底沒有思路的時候,《圖解Java多線程設計模式》救了本身一命。架構
PS:這本書北航圖書館有電子館藏,你們能夠搜一下下載。(涉及版權問題就不直接放連接了)函數
具體以下:測試
模式角色 | 對應Class | 做用 |
生產者 | TestMain | 提供請求 |
消費者 | Copying | 不斷地查詢是否有新的請求須要執行 |
放有產品的桌子 | Elevator | 爲生產者和消費者提供各類各樣的接口 |
產品 | PersonRequest | (做業提供/自帶) |
類圖:this
第二次做業的架構能夠說和第一次徹底同樣。可是這裏本身遇到的一個新的考慮是是否須要換成觀察者模式。spa
再也不是每層都本身執行一遍有無請求的查找函數而是當有新的請求加入的時候,通知電梯這個請求的到來。這樣沒必要每一層都執行查找函數。線程
關於觀察者模式和消費者模式的區別:
目前看來,最大的區別在於一個是本身去看生產者有沒有請求,一個是由生產者主動通知本身有沒有請求。
這三次做業本身沒有改變本身的架構,這樣的帶來的問題是CPU運行的時間會長一些。
目前尚未看到觀察者模式在此次做業上有特別須要的地方。(由於電梯每一層都必須輸出一個arrive信息。這樣,電梯仍是沒法作到請求到請求的代碼運行?)
類圖:
【請求拆分】
爲了沿用本身以前的架構,第三次做業發現請求拆分比較方便。
即一開始就把一個電梯沒法到達的請求拆成兩個。
而後,優先尋找中轉樓層爲兩個電梯之間的樓層。若是沒有,選擇中轉樓層爲1或者15 的最近樓層。
接下來的運行過程就和單電梯的調度策略是同樣的了
踩到的坑:
一、LOCK對象與wait notify對象不一致就沒有用了。
好比
若是是
synchronized (lock)
就必須在裏面使用
lock.wait();
而非簡單地
wait();
由於wait,表示是當前的this對象等待。
同理,notifyall也須要一致的對象。
二、電梯線程像屍鬼同樣死不了,或者死的過早的問題。
若是主線程結束了,電梯線程怎麼知道主線程結束了呢?
須要使用一個dead信號量,電梯線程不斷地探測,若是爲true,就自殺。
可是,出現了三個電梯,就比較難辦了。由於一旦沒有任務,電梯就wait了嗎,不會主動探測到dead信號量。因爲是事先分配全部請求,若是主線程輸入沒有了,其餘電梯隊列裏面也不會有扔給本身的了,就能夠線程結束。可是要當心電梯wait而檢測不到dead信號量的問題。
其實能夠發現,第一次做業除了copy 的run函數複雜度比較高之外,實際上出現問題的就只有
elevator的stop方法,的確,因爲幾乎調用了這個類其餘全部的函數,這個函數的獨立路徑的條數較多,模塊的複雜性相比之下最大。
兩個線程的協做,其中,輸入線程(主線程)TestMain一直沒有wait,不斷地將請求加入請求隊列當中並喚醒copying線程,copying線程就一直不斷地執行請求,直到請求隊列爲空將本身sleep。
renewdirect函數很是地冗長,主要的複雜度最大的函數。不過,這個函數的基本複雜度並不高(非結構化成分並很少),可是這個函數的模塊斷定結構複雜度很高,若是須要預防錯誤所需測試的最少路徑條數較多,程序比較難以維護。
第二次做業線程之間的協做關係和第一次做業的協做關係基本如出一轍。只不過增長了捎帶的算法
因爲此次做業,代碼須要處理的邏輯增長了很多可是本身依然只用了兩個類,增長的函數也並很少,就致使每一個函數都比較地冗長,複雜度較前兩次做業大大地上升。
第三次做業,增長了線程池這個結構,管理三個電梯,因爲只有一把鎖,三個電梯在訪問請求隊列時會造成競爭關係。
主線程依然在不斷地運行直到輸入結束。
三個電梯各自的運行邏輯跟前兩次做業類似,只不過最後結束的時候,並不僅僅要判斷dead信號量,還要檢查其餘兩個電梯的隊列裏面是否有本身須要處理的請求。
不出意料地第三次複雜度最高。第三次本身的幾乎所有代碼量都集中到Controller類裏面了。分佈很是地不均衡.....
感受多線程在電梯做業中最核心的一點就是把握住lock的申請與釋放問題。只要sleep就再也不佔有lock了。目前看來仍是能夠生命能夠承受之重的代碼。
但願下次不要再那麼臃腫了.....