第二單元是咱們學習oo以來第一次接觸多線程。這一單元的三次做業和之前同樣,採用了難度遞進的方式,並且前兩次做業的設計思路在第三次做業都多多少少有些體現(或者說是在其基礎上作出的改進)。因此此次博客將以第三次做業爲主,對這一單元的做業進行分析。算法
第一次做業是傻瓜電梯的設計,我就嚴格按照,先來先服務,一次只有一我的在電梯裏來設計。第二次增長了捎帶請求,也就是說,電梯再也不只能有一我的,而是有一個主乘客(主請求),和多個從屬乘客(從屬請求)。這裏的主請求和第一次做業裏的只有一個請求時候的那一個惟一的請求其實起到的是相同的做用,就是說實際操控電梯的其實是他,而電梯中的其他從屬請求能作的只是在他要進入或者要出去的時候,命令電梯開關門供他進出。固然,從屬請求也有可能成爲控制電梯實際運行的主請求,那就是在主請求已經到達目的地,而從屬請求尚未達到的時候。這兩次做業其實大體上沒多大區別,只是第二次做業在調度方法上作了改進,但整體上看,都是兩個線程(主線程,在我這兒也就是輸入線程,電梯線程)的併發。他們之間的關係與生產者消費者很類似,輸入線程往請求隊列裏投放請求,電梯線程從請求隊列裏拿出請求,這個請求隊列就是兩個線程都會訪問的臨界資源,須要使用synchronized將其鎖住,防止兩個進程同時對其進行讀寫的狀況發生。輸入線程向請求隊列輸入請求是在任什麼時候候均可以進行的,至於電梯線程何時從請求隊列取出請求,那就是調度器的職責了。安全
接下來具體講一下第三次做業的設計策略。與前兩次做業不一樣,此次做業在之前的基礎上由一部電梯改爲了三部電梯,並且由之前的電梯每一層均可以到達,變成了不一樣電梯能夠到達特定樓層。多線程
這樣就會出現這種狀況,有一些請求沒法在一部電梯裏完成,而是須要多部電梯配合完成。好比一個從-1樓到3樓的請求,電梯A能夠到-1樓和1樓可是到不了3樓,電梯C能夠到1樓和3樓可是到不了-1樓,這是單獨的A、C都完成不了任務,可是讓A、C合做,A把乘客從-1運到1,再讓把乘客從1運到3,就完成了這個請求。因此一個最容易想到,也最容易實現的方法,就是A、B、C三部電梯各自有本身的請求隊列,在輸入一個請求時,由調度器決定這個請求應該去往哪一個隊列。要是能用一部電梯就解決的請求,那就只用一部電梯,好比-1樓到1樓就用A電梯。若是是一部電梯沒法解決,好比先前提到的-1到3樓的例子,那就把這個請求拆分紅兩個請求,一個是-1到1樓的請求,一個是1到3樓的請求,而後這兩個請求分別進入A、C兩部電梯的等待隊列。這裏還有一個問題,就是1到3樓必須在-1到1樓的請求執行完成以後執行,個人解決方法是每部電梯設置兩個等待隊列,一個是就緒隊列,一個未就緒隊列。就緒隊列和咱們以前的隊列沒有分別。而未就緒隊列中的請求都是從一個主線程輸入的請求拆分紅兩個請求中的一個,而與它對應的另外一個請求在就緒隊列中,只有等就緒隊列中的這個請求已經執行完成後,這個未就緒隊列中的請求才能進入就緒隊列,等待調度器調度給電梯。仍是用-1到3樓的例子來解釋,在這個例子中,我讓1到3樓的請求先到C電梯的未就緒隊列,-1到1樓的請求到A電梯的就緒隊列,這時,調度器能夠再合適的時機把-1到1樓的請求給A電梯,可是1到3樓的請求被分配到C電梯的等待隊列,只有等-1到1樓的請求執行完了也就是說乘客已經從電梯出來,處於1樓了,它才能進入就緒隊列,纔有從1樓又進入C電梯的資格。架構
這種算法容易實現,可是隻考慮了正確性的問題,它的性能是不好的。好比設想這樣一種狀況:一個從1樓到15樓的請求,調度器把這個請求分給了A電梯,在乘客進入A電梯,電梯關門後,又有一個1到15樓的請求到達請求隊列,調度器又會把這個請求分配給A電梯,那麼A只能把第一個乘客送到15樓,再返回1樓,把第二個乘客送到15樓。1到15樓的請求本來是A、B電梯均可以單獨完成的,可是因爲調度器的調度方式,是隻根據請求自身而不顧電梯的狀態來拆分請求。所以,雖然B電梯也能夠完成這個任務,但因爲調度方式的緣由,明明B沒人用,A處於忙碌狀態,可是調度器認定了A電梯,就一根筋的只把任務分配給A,致使了電梯資源的浪費。併發
1.代碼統計性能
method ev(G) iv(G) v(G)學習
Dispatcher.Dispatcher() | 1.0 | 1.0 | 1.0 |
Dispatcher.geta() | 1.0 | 4.0 | 8.0 |
Dispatcher.getb() | 1.0 | 4.0 | 8.0 |
Dispatcher.getc() | 1.0 | 4.0 | 8.0 |
Dispatcher.getRequestsA() | 1.0 | 1.0 | 1.0 |
Dispatcher.getRequestsB() | 1.0 | 1.0 | 1.0 |
Dispatcher.getRequestsC() | 1.0 | 1.0 | 1.0 |
Dispatcher.getWaita() | 1.0 | 1.0 | 1.0 |
Dispatcher.getWaitb() | 1.0 | 1.0 | 1.0 |
Dispatcher.getWaitc() | 1.0 | 1.0 | 1.0 |
Dispatcher.put(PersonRequest) | 1.0 | 4.0 | 4.0 |
Dispatcher.requestsadc() | 1.0 | 2.0 | 3.0 |
Dispatcher.setExit() | 1.0 | 1.0 | 1.0 |
Dispatcher.sort1(PersonRequest) | 1.0 | 23.0 | 23.0 |
Dispatcher.sort2(PersonRequest) | 1.0 | 9.0 | 9.0 |
Dispatcher.sort3(PersonRequest) | 1.0 | 6.0 | 7.0 |
Dispatcher.waitsadc() | 1.0 | 3.0 | 3.0 |
Elevator.arrive() | 1.0 | 19.0 | 20.0 |
Elevator.call() | 1.0 | 5.0 | 5.0 |
Elevator.close() | 1.0 | 14.0 | 15.0 |
Elevator.downfloor() | 1.0 | 1.0 | 2.0 |
Elevator.Elevator(Dispatcher,int,int) | 1.0 | 1.0 | 1.0 |
Elevator.getFloor() | 1.0 | 1.0 | 1.0 |
Elevator.getThread() | 1.0 | 1.0 | 1.0 |
Elevator.in(int) | 1.0 | 1.0 | 1.0 |
Elevator.move() | 1.0 | 5.0 | 5.0 |
Elevator.open() | 1.0 | 2.0 | 2.0 |
Elevator.out(int) | 1.0 | 7.0 | 7.0 |
Elevator.run() | 1.0 | 6.0 | 6.0 |
Elevator.sleep() | 1.0 | 2.0 | 2.0 |
Elevator.start() | 1.0 | 2.0 | 2.0 |
Elevator.threadsleep(int) | 1.0 | 2.0 | 2.0 |
Elevator.upfloor() | 1.0 | 1.0 | 2.0 |
Main.main(String[]) | 3.0 | 3.0 | 3.0 |
Total | 36.0 | 140.0 | 158.0 |
Average | 1.0588235294117647 | 4.117647058823529 | 4.647058823529412 |
2.類圖優化
運行Main類的初始進程便是三部電梯線程的父進程(建立三個電梯進程),同時扮演着輸入線程的角色。他不斷向請求隊列(請求隊列位於調度器中)輸入請求。再有調度器處理這個請求,將處理後的1~2個請求加入到A、B、C的就緒隊列和未就緒隊列中。最後電梯線程執行到適當的時機,調度器會根據狀況把這部電梯的就緒隊列中的請求給到相應電梯來執行,或者將未就緒隊列中的請求移動到就緒隊列中。這種結構好處在於簡單明瞭,只有三個類,分別負責請求輸入,電梯運行和電梯調度,分工明確,思路簡單。可是這種寫法帶來的弊端就是調度器類過於冗長,在寫調度器的時候容易顧此失彼。並且因爲調度器所有都寫在一個類中,修改時要考慮的東西不少,改debug和優化帶來了不小的難度。spa
多線程與單線程最大的不一樣在於線程安全問題。單線程你只須要想着你在這一個線程中所要實現的功能,若是要上難度,也只會是在語法上上難度。而多線程不一樣,你必須考慮整體架構,必須考慮線程間的通信,考慮不一樣線程的同步與互斥。因此在設計時,咱們要考慮設計的原則問題:單一責任原則、開放封閉原則、里氏替換原則、接口分離原則、依賴倒置原則。線程