轉眼已經是第九周,第二單元的電梯系列做業已經結束,終於體驗了一番多線程電梯之旅。java
第一次做業是單電梯的傻瓜調度,雖然是第一次寫多線程,但在課程PPT的指引下,寫起來仍是很是容易;第二次做業是單電梯的捎帶調度,並加入了負層電梯,寫起來也相對容易,不過在寫捎帶策略時容易出不少BUG;第三次做業是多電梯協做調度,不一樣電梯有不一樣的停靠樓層、容量等,看起來好像比較難,但其實只要將請求拆分,而且有第二次做業的代碼基礎,須要大改的也基本上只有調度器而已。安全
相比於第一單元藉助延時才完成做業,這一單元的做業我都及時經過了中弱測。多線程
一、第一次做業架構
第一次做業的電梯沒有那麼複雜,採用的是PPT中的生產者消費者模式,設計有一個電梯類做爲電梯線程,一個請求類做爲輸入線程,一個隊列類用來實現電梯與輸入的交互,還有一個主類。性能
隊列只存一個請求,電梯空閒時從隊列中get一個請求執行,若隊列中沒有請求電梯會wait,直至有新請求來時notify;若隊列中已有一條待執行請求,新請求會wait,直至隊列中那條請求被電梯獲取時notify。測試
至於電梯線程,只是簡單地先sleep從電梯當前層到請求出發層的時間,而後開門,進人,再sleep開關門時間,關門,而後sleep從出發層到目標層時間,開門,出人,sleep開關門時間,關門。很是簡單的調度。優化
二、第二次做業spa
第二次做業因爲要捎帶,調度器(即請求隊列)類改成用arraylist存儲請求。線程
每當有新請求,直接存入調度器,而且notifyAll一下,電梯空閒時獲取隊列第一條請求,隊列爲空時wait。設計
重點是電梯線程。我將電梯的運行分爲從當前樓層到主請求出發樓層和從出發樓層到目標樓層兩步。在從當前樓層到出發樓層這一步,只捎帶出發樓層和目標樓層在這一過程之間的請求,也就是捎帶請求的目標樓層不能超過這一步的目標樓層;在第二步從主請求出發樓層到主請求目標樓層時,就會將全部通過樓層而且方向與主請求方向相同的請求捎帶,即捎帶請求的目標樓層能夠超過主請求目標樓層,實現更多的捎帶。
至於捎帶原則,我會在電梯到達每一層時從請求隊列中尋找從當前樓層出發而且符合其餘條件的請求。在電梯到達每一層時,從請求隊列裏尋找,而後根據找到捎帶請求和目前電梯裏的請求的上下樓狀況判斷是否要在當前層開門,若要開門,須要在sleep開關門的時間後再從請求隊列裏尋找一次捎帶請求(由於存在電梯開關門之間有新的能夠捎帶的請求到來,這樣能夠捎帶更多請求)。
三、第三次做業
第三次做業有三部電梯,調度器中設置了三個請求隊列。每當有新請求,若請求有電梯能直達,就存入相應電梯隊列;若不能直達,則拆分爲兩步,分別存入兩部電梯的請求隊列。拆分原則比較簡單,也比較機械,好比拆分後的某一步如有兩部電梯都能完成,只是固定地把他分配給其中的一部(若是有C電梯的話優先分配給C電梯,由於C電梯可以直接完成的請求很少)。
這一次個人調度策略是請求優先,就是若是一條請求須要兩部電梯完成,那麼兩部電梯要同時出發去相應樓層完成這一請求。好比一條請求要從4到20層,我會把請求拆分紅讓B把他先送到15層,再讓A把他送到20層,這時,在B送他時,A電梯也會出發去15層等他到達15層,再直接把他送到20層。但考慮到另外一種狀況,若是有4到20層的請求,不久後來了一個A電梯能直達完成的請求(假設不能捎帶),那麼A電梯仍是會到15層等待第一條請求的到來,而不會完成第二條指令,直至把第一條請求送到20層,再去接第二條請求。這樣可能會增長電梯的運行時間。總的來講,個人調度策略在請求較少時性能比較好,請求多的話反而會下降性能。(強測大部分點仍是指令不少的狀況,因此我至關於作了一次負優化)
至於捎帶策略,就是在每部電梯運行時,捎帶上出發樓層和目標樓層在當前運行區間以內,而且方向相同的請求。其餘捎帶細節與第二次做業相同。
一、第一次做業
第一次做業的代碼很是簡單,比較蠢的是我在第一次做業是沒有注意到給的輸入接口中的方法,而是用split方法手動拆分請求,多寫了很多代碼。
二、第二次做業
第二次做業我寫的最複雜的是尋找可捎帶請求的方法,每次捎帶時都要遍歷整個請求隊列尋找,並且因爲在電梯從當前樓層到主請求出發樓層和從主請求出發樓層到主請求目標樓層兩個過程的捎帶條件不一樣,每次尋找還要判斷是哪種捎帶。其次是上下行的電梯調度方法,一個從電梯出發樓層到目標樓層的for循環,每一層都要判斷是否開門,是否要跳過這一層(0層和第一步調度的最後一層等),是否要更改目標樓層等。
三、第三次做業
第三次做業最複雜的是調度器的拆分方法,用if-else語句實現全部狀況的請求拆分。其次尋找捎帶請求方法和調度方法與第二次做業問題相同。
第一次做業強測和互測沒有BUG。
第二次做業原地爆炸。強測直接四十多分,互測被刀了27刀。這些都源於兩處BUG。第一處是,在電梯從當前樓層到請求出發樓層時,主請求還未進入電梯,我在每一層判斷是否要開門時,考慮到了通過主請求目標樓層而主請求還未進入電梯的狀況,而在輸出上下電梯狀況時沒有考慮到。也就是說若是在主請求目標樓層有其它請求要上下,電梯會開門,並且會輸出主請求OUT的信息,即人未進入電梯,我卻將他放出電梯。第二處是,我在尋找捎帶請求時,判斷電梯當前運行方向,是讓電梯當前樓層與當前目標樓層作比較,若當前樓層小於目標樓層,就是在上行,不然下行。也就是說,若是電梯在上行時運行到了目標樓層,即當前樓層與目標樓層相等,我會判他爲下行,因而尋找向下走的可捎帶請求,而將一些不應捎帶進來的請求捎帶進來,會出現重複進電梯的狀況。
第三次做業吸收了第二次做業的教訓,最後強測和互測都沒有BUG點。
第一次做業比較簡單,最終也是100分,沒有找別人的BUG。
第二三次做業都是本身構造一些易錯的輸入,主要針對電梯運行邊界的捎帶狀況,電梯容量等方面設計輸入。
這樣手動構造雖然效率不高,但可以考慮到每個容易出錯的點。在上週的研討課上有同窗講了用java寫判斷輸出是否正確的方法,可以隨機生成輸入,並判斷輸出是否符合各項要求的方法,這樣即便不會寫對拍器也能夠進行自動測試。
第一次接觸多線程,從最初的憂慮忌憚,到後來逐漸駕輕就熟,整個探索的過程仍是比較充實的。寫第一次做業時,我反覆看課程PPT看了幾乎一成天才大致搞懂wait()和notifyAll()的用法,後來也對這些用法和線程安全等問題慢慢熟悉,逐漸掌握了多線程的寫法。
除了課程的心得,從這一單元的做業中我還要吸收一個很重要的教訓——對於本身代碼的測試。寫完第二次做業,中弱測所有經過後,我就很自信地沒有測試本身的代碼,其實中弱測是很弱的,即使所有經過,代碼也可能存在不少問題,果真強測直接開花。另外寫代碼時其實也要注意,儘可能減小因爲粗心寫出的BUG。