本單元的作業總體來說比較愉快,畢竟不像上次一樣次次重構。
本單元為電梯系列問題,涉及到多執行緒問題。簡單起見,我使用的是生產者-消費者模式。
本次作業要求實現單部可稍帶電梯。看完題目後我認為生產者-消費者模式非常適合解決這個問題。
本次電梯我採用的是look方法。本方法核心即在於電梯方向的判斷,這在dispatcher
中的rstjudge()
方法有體現。具體說來,電梯會一路捎帶當前執行方向上(當然,要在電梯暫時不反向的情況下可以到達才行)的所有請求。若沒有找到這樣的請求,才會去注意反方向的請求。
類:
dispatcher
:排程器類,也即托盤類,可獲取佇列中的請求以及分配請求給電梯,新增請求和分配請求方法均上鎖。
elevator
:電梯類,也即消費者
main
:主類,也沒啥好說的,不過它也被用來幹一些奇怪的事(比如儲存全域性變數之類的)
inputdeal
: 管理輸入,主要從輸入中讀取請求,也即生產者
requestqueue
: 用於儲存請求的佇列,並定義了一些相關的操作。另外,該類是乙個單例,只有dispatcher能對其進行操作。
類圖:
其時序圖大致如下:
度量分析如下:
可以看出核心方法的複雜度過高。
分析:
本次作業有乙個最大的問題即是如何結束執行緒。我採用了乙個全域性變數來標記輸入是否結束,當且僅當輸入結束而且請求佇列和電梯內均為空時,電梯執行緒才會退出。
bug:
本次強測和互測均未發現bug,互測時純隨機測資料,也未發現他人bug。
本次作業加入了新的電梯,並且加入了乘客數限制,相對的,正確性判斷中的效能則不再與某一策略的時間進行比較,應該說是放寬了。
在不考慮效能的情況下,這次作業極為簡單,僅僅對第一次的**進行少量修改即可完成。本次我基本上沿用了第一次的策略。其時序圖與第一次基本一致,類圖和度量分析如下:
問題基本上同第一次作業,值得注意的是這次elevator.run()
為紅,原因在後面解釋。
分析:
這次本打算考慮如何能有更好的效能,但是我想到的任何可以不是很複雜地實現的方法基本都有兩面性,於是造成了我的選擇困難,最後乾脆就直接沿用了第一次的排程。具體來說就是每個電梯獨立地按照第一次作業中所定義的行為行動。唯一的區別即在於某個電梯超載時就會無視任何請求。由於我的電梯並沒有預分配請求,而是在每一層來判斷請求位置並隨之移動,這也就導致了這些電梯行為模式幾乎一樣,會幾個電梯一同去接某一請求,但只有乙個電梯能接上。顯然這樣會使效能有很大的下降(但是超載情況下效能會爆發式增長)。經過試驗,我發現在建立電梯的時候讓它們相互錯開一段距離(這就是run()
方法飆紅的原因)可以部分破壞它們的同步,大多數情況下效能會有一些提公升。
然後,理所當然的,我這次效能基本就取決於是否超載了,但是最後強測得分居然還能看。
bug:
強測未發現bug,互測也是隨機方式,未發現他人bug,然而自己卻被hack了一次。於是,玄學bug出現了。我在本地測試無法復現,而且我十分確認我把應該加鎖的地方都加鎖了並且在應該喚醒的地方都喚醒了。從輸出來看,評測的時候我並沒有產生死鎖,更像是一些出現概率極低的邏輯錯誤。由於我沿用了第一次的**,而第一次並未出錯(甚至第三次沿用了第二次且第三次也未出錯),我一度懷疑甚至是評測機的鍋。當然,最後我也沒能找出bug,原封不動地重交了一遍後過了。
本次作業加入了可到達樓層與電梯種類,同時可以動態增加電梯。為了實現動態增加,除開始的三個電梯外,改為由dispatcher
來建立電梯。除此之外基本沿用第二次的策略,除前所述以及結束外,時序圖基本與第一次一致。類圖和度量分析如下:
可以看到複雜度與第二次基本一致,但是有些方法由於需要一些額外的判斷,複雜度不可避免地會上公升。
分析:
本次作業為了保證正確性而並未過多考慮效能。總體仍然採用look模式。為簡化換乘,我指定了1樓和15樓為換乘地點,需要換乘時會就近選擇換乘點。每個電梯的邏輯都是相同的,仍然是自由競爭的模式。但是每個電梯只會在意自己能夠直達或是沒有任何電梯能夠直達的請求。在換乘時請求會由電梯來丟擲,加入到請求佇列中(為保證同步,此過程由dispatcher進行)。本次作業最大的問題之一也是如何判斷停止。採用前一次的判斷方法顯然是不行的,因為會出現需要換乘的情況。只有當輸入結束、請求佇列空、所有電梯均空的時候才能結束。
個人認為設計主要是未滿足srp原則,部分類(主要是排程器和請求佇列之間)的功能有些許交叉。其他如開閉原則,由於基本排程全由排程器實現而且分工明確,擴充套件並無需大幅修改;由於本次**中關係簡單且並無繼承,也僅有runnable介面,故運用其他3個原則進行分析我認為意義不大。如果說不考慮效能,那麼我自認為本次作業還是有一定的擴充套件性的。但考慮效能的話就不能看了。
bug:
強測和互測均未被發現bug。互測中我想到如果我在加電梯後不加人很可能會導致一些粗心的小朋友死鎖,於是交了乙個這樣的資料成功地讓乙個人死鎖了。另外由於這樣操作的話最後一條排程時間與最後一條輸入時間無關,於是我嘗試性地交了乙個215s加電梯的資料,本意是想坑一下那種等到輸入全部結束後才開始一波帶走的小朋友們,然後由於評測時根本不管最後一條排程的時間而是執行時間超時直接判斷tle,結果出現了出乎意料的1穿7場景,雖然後來理所當然地被清掉了,但是看到的一瞬間覺得真的爽啊。
本單元作業讓我體會到了與以前完全不同的乙個世界。在多執行緒的世界裡,無論是**寫法、架構設計還是debug方法都與單執行緒有著極大的區別。考慮死鎖、同步、阻塞等問題也是樂趣無窮。在這裡,我也學到了很多設計模式並領略了它們的魅力。同時本單元的三次作業讓我經歷了一次完整的迭代開發而不是重構,讓我對自己的設計能力更有信心了。
OO第二單元總結
共享資料類 在總結後面的3.基於度量的程式結構分析部分,本人根據展示的uml類圖更加詳細的講解了具體的協同結構工作原理。通過對實現以上操作的共享資料類中的方法設定synchronized,從而實現執行緒對共享資料的訪問同步。ocplsp ispdip 根據以上類圖,分析本次作業設計思路如下 2 根據...
OO第二單元總結
第二單元總結 第一次作業 思路與反思 uml類圖 度量分析 耦合度 第二次作業 思路 第二次作業與第一次的迭代在於電梯增加 人數限制 樓層改變,我依舊用的look演算法,在第一次作業的基礎上修改細節即可,多部電梯要求實現執行緒安全,由於我使用的look演算法,電梯盲目執行,沒有更高階的排程,只需要在...
OO第二單元總結
第一次作業 1.設計策略 程式採用生產者 消費者模式,一共有兩個執行緒,乙個是主線程,主要負責輸入 另乙個是電梯執行緒,用來處理請求。此外,程式還有乙個核心 排程器,內建請求佇列和排程函式,相當於托盤。主線程不斷將請求輸入到排程器中,而電梯執行緒每完成 上樓 開門 出人 進人 關門 的一次迴圈就從呼...