本單元考察基於多線程的電梯調度問題,成功讓我從一個多線程小白到了基本掌握了使用鎖來控制線程安全的能力,收穫頗多(充分體驗了迷茫地de一個又一個死鎖bug的痛苦)。算法
三次做業的關鍵以下:安全
第一次做業:單臺電梯的調度,電梯可到達全部樓層,容量不設限,考慮捎帶。多線程
第二次做業:多臺電梯的調度,經過輸入控制電梯臺數,電梯可到達全部樓層,容量受限,考慮捎帶。性能
第三次做業:3+n臺電梯的調度,經過輸入隨時增長電梯,電梯到達樓層、容量、運行時間分類受限,考慮換乘和捎帶。測試
本單元的電梯設計,我主要是利用了生產者消費者模式,將電梯需求做爲生產者,電梯運載乘客做爲消費者,將調度器做爲托盤,處理需求並反饋當前需求信息,並將調度器設爲單例模式,做爲共享對象,供全部類訪問。優化
對於請求隊列的設計,我將總的請求隊列存在調度器中,分爲上行隊列和下行隊列,併爲電梯設計本身的內部隊列,存儲已經進入電梯的需求。電梯在輸入結束,而且全部隊列均爲空時,結束電梯線程。spa
總的工做模式,是採用輸入將需求加入調度器中的請求隊列,電梯根據調度器所返回的請求信息,主動向調度器申請請求的方式。線程
第一次做業:我採用了主線程和電梯線程的雙線程模式,將輸入放在主線程中,在主線程中進行電梯結束的判斷,在電梯類中設置特定結束方法。設計
第二次做業:我爲輸入設計了一個單獨的線程,每部電梯單獨擁有一個線程,在主線程中建立輸入和電梯線程,在調度器中設置靜態變量的輸入結束標誌,仍在主線程中判斷並結束電梯線程。對象
第三次做業:在上一次的基礎上,將ABC三部電梯的建立放在主線程中,將臨時電梯的建立放在輸入線程中,輸入線程將新增電梯返回給主線程,仍在主線程中結束電梯線程。 對於換乘需求,因爲本次做業的換乘需求均可以經過一次換乘達到,我在調度器中新建了換乘隊列,在有電梯申請請求時判斷是否可經過此電梯進行換乘,併爲此請求設置換乘層,在從換乘層出來後,將換乘請求加入主請求隊列中。
吸收了上一單元的教訓,在本單元三次做業的過程當中,因爲我在設計之初便考慮着電梯的擴展性,並且後續做業的方向也比較好推測,我並無進行重構,只是每次都加了一些屬性或方法來實現新的要求。
代碼行數變化:254→445→796
SOLID | My Project | |
---|---|---|
SRP | 單一責任原則 | 調度器進行需求調度,輸入只負責得到請求,電梯只負責電梯的運行即出入,基本符合 |
OCP | 開放封閉原則 | 三次做業的遞進過程當中,爲了方便,加以習慣因素,都是採起在原有類中增長新方法的方式,沒有很好地貫徹此原則 |
LSP | 里氏替換原則 | 第三次做業對電梯類進行了擴展,沒有違反此原則 |
ISP | 接口隔離原則 | 沒有實現接口 |
DIP | 依賴倒置原則 | 全部類都依賴調度器類 |
因爲本單元的結構在第二次做業並無改變,故直接貼出最後兩次做業的項目UML類圖。
在第三次做業中,因爲將電梯分爲ABC三類,故將原電梯擴展了三個子類,在每一個子類中分別設置其特有屬性(容量、可停靠層等),並新建了Person類。以前的兩次做業一直在採用嵌套的ArrayList來儲存需求信息,但第三次做業需求的屬性更多了,同時也爲了知足老師上課所講的顯式表達原則,故改變了原來的表示方式,這個的改動很是簡單,我也能夠明顯感覺到了此改變所帶來的操做的方便性。
至於線程之間的協做關係,畫出來的簡單UML協做圖以下
關於各個方法的複雜度分析以下(略去了一些簡單的get和set方法),能夠看出,大部分的方法複雜度都不是很高,只有調度器中的幾個方法有較高的耦合度和複雜度,這可能歸咎於優化時增長的一些判斷方法來減小電梯的開關門次數,目前沒有太好的解決辦法
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
Controller.Controller() | 1 | 1 | 1 |
Controller.add(PersonRequest) | 2 | 3 | 3 |
Controller.addTransQueue(Person,int,char) | 2 | 3 | 4 |
Controller.addTransferPerson(Person) | 1 | 2 | 2 |
Controller.canInDirection(char,Person) | 3 | 2 | 3 |
Controller.canInFloor(char,int,Person) | 1 | 2 | 2 |
Controller.canTransferInFloor(int,char,int,Person) | 3 | 3 | 3 |
Controller.getDirectArrive(int,int) | 4 | 1 | 4 |
Controller.getInstance() | 1 | 1 | 3 |
Controller.getPeopleIn(int,int,char,int) | 10 | 8 | 12 |
Controller.getTansToFloor(char) | 4 | 3 | 4 |
Controller.getTransferFloor(Person,char) | 8 | 4 | 8 |
Controller.havePeople(int,int,char) | 8 | 6 | 8 |
Controller.havePeopleIn(int,int,char) | 7 | 6 | 8 |
Controller.isEmpty() | 1 | 4 | 4 |
Elevator.Elevator(String) | 1 | 1 | 1 |
Elevator.arrive() | 1 | 1 | 1 |
Elevator.changeDirection() | 4 | 4 | 7 |
Elevator.closeDoor() | 1 | 1 | 1 |
Elevator.elevatorRun() | 2 | 1 | 3 |
Elevator.end() | 1 | 2 | 3 |
Elevator.getPeopleSize() | 2 | 2 | 3 |
Elevator.isEmpty() | 1 | 2 | 2 |
Elevator.isNeedOpen() | 4 | 5 | 8 |
Elevator.isNeedWait() | 9 | 7 | 14 |
Elevator.open() | 1 | 2 | 2 |
Elevator.openDoor() | 1 | 1 | 1 |
Elevator.peopleIn() | 1 | 5 | 5 |
Elevator.peopleOut() | 1 | 4 | 4 |
Elevator.run() | 4 | 6 | 9 |
Elevator.waitWorking(Integer) | 1 | 1 | 2 |
ElevatorA.ElevatorA(String) | 1 | 1 | 1 |
ElevatorB.ElevatorB(String) | 1 | 1 | 1 |
ElevatorC.ElevatorC(String) | 1 | 1 | 1 |
Input.Input() | 1 | 1 | 1 |
Input.addElevator(ElevatorRequest) | 1 | 2 | 4 |
Input.getElevators() | 1 | 1 | 1 |
Input.run() | 3 | 5 | 6 |
Main.main(String[]) | 1 | 4 | 4 |
Person.Person(int,int,int) | 1 | 1 | 1 |
Person.equals(Object) | 3 | 1 | 8 |
第一次做業比較簡單,順利經過了全部強測點,也沒被別人hack到,只是可能因爲我只會讓與電梯運行方向相同的人上電梯,也是考慮到之後可能會限制電梯容量,性能分不是特別高,如今想一想第一次仍是應該讓全部人都上電梯,後面再改。
第二次做業,因爲我測試不充分,在優化會有多部電梯對同一請求開門時,我採起了判斷電梯是否在當前層開門時,提早將請求取進電梯的預定序列的方式,但因爲我在過程當中有屢次判斷,致使了後來的請求會把以前的請求覆蓋的bug,由於這個bug,我成了全屋惟一被hack的人。。。雖然bug很好解決,但形成的損失也是巨大的,給我敲響了警鐘。還有就是出現機率很低的死鎖問題,我被僅有一條請求的輸入hack到了,可是本地並沒有法復現,在bug修復時也直接經過了,因此我並無深究,可能這也致使我在第三次做業的bug。
第三次做業,我被hack到了多個RTLE的問題,就是死鎖了,本地復現率也很高,通過我屢次嘗試, 發現竟是個人結束判斷有問題!我一開始是在結束時產生interrupt信號將wait中斷,致使了我在第三次做業有一個循環並不會被interrupt斷掉,而在出了while循環後錯過了中斷時機致使陷入無限的等待中,基於此問題,我只能新增了一個interrupt變量,經過進行變量的判斷來選擇何時出循環、結束線程等。
在hack別人的道路上, 我走得也很艱辛。首先,其它同窗的代碼邏輯有些與我徹底不一樣,讀懂別人代碼後我就處於一種很懵的狀態了,試了一些本身以爲模糊的點卻也發現人家的代碼都是對的,有些性能不是很好但我也hack不到,考慮到此次數據的複雜性, 我不認爲評測機能起到很好的做用(第一次我也每靠評測機hack到別人,反而是本身構造的數據更容易hack到別人),因此我沒有花時間去建評測機。
其次,我嘗試了一些我認爲可能會出現調度問題的測試點,但可能個人想法仍是不夠全面,我在這個方向上也沒有hack到別人。關於死鎖問題,直到最後,我才意識到是怎麼一回事,更加沒法經過這個去hack別人。
總之,此次的互測我是比較失敗的,沒有很好get到此次互測的精髓所在,這和我不紮實的多線程的知識有關,只想着經過設計策略去hack別人而沒有利用多線程的程序特色,尚未從第一單元走出來。
在本單元的設計中, 我沒有特地去採起高超的算法去優化,也沒有去對每種換乘策略進行設計,只是作了一些必要的優化,好比第二次做業個人全部電梯都會跑向有須要的樓層,但只有一臺電梯會開門 ,這是考慮到需求所在樓層產生的隨機性,因此並無只讓一臺電梯去接人;第三次做業,我也僅是在電梯申請需求時,判斷換乘需求是否能夠經過此電梯進行換乘,並無提早爲每位乘客設計最優的換乘策略,這也是考慮到防止有些電梯過忙而延長乘客等待時間,得不償失。總之,雖然個人優化工做不多,但也是在合理的考慮之下的,雖然確定比不上優化到極致的大佬,但我也發現個人策略最後獲得了較高的性能分,這也印證了隨機是個好東西。