[BUAA OO Unit 2 HW8] 第二单元总结
文章目錄
- 前言
- 第一次作業(yè)
- 架構(gòu)
- Producer-Consumer模型
- 調(diào)度策略
- 類(lèi)圖和時(shí)序圖
- 鎖和同步
- 復(fù)雜度分析
- tips
- 二次詢問(wèn)
- 電梯反轉(zhuǎn)
- 開(kāi)門(mén)判斷
- HashMap遍歷刪除
- 些許優(yōu)化
- Bug分析
- 第二次作業(yè)
- 架構(gòu)
- 調(diào)度器
- 換乘 + 線程結(jié)束
- 類(lèi)圖和時(shí)序圖
- 復(fù)雜度分析
- tips
- Bug分析
- 第三次作業(yè)
- 架構(gòu)
- 功能實(shí)現(xiàn)
- 可達(dá)性
- 樓層限制
- Semaphore
- 實(shí)現(xiàn)
- 復(fù)雜度分析
- Bug分析
- 心得體會(huì)
- 參考
前言
第二單元的主題是電梯調(diào)度問(wèn)題,主要是初步學(xué)習(xí)多線程的編程思想,解決線程交互和線程安全的問(wèn)題。早就聽(tīng)說(shuō)第二單元多線程是OO的一座大山,榮文戈老師也說(shuō)過(guò)以后上班遇到的程序基本上全都和多線程掛鉤,所以多線程的學(xué)習(xí)是很關(guān)鍵的,給哪個(gè)對(duì)象加鎖,如何正確且高效地給對(duì)象加鎖,都是值得思考的問(wèn)題。
這一單元的三次作業(yè)也是層層遞進(jìn),逐步實(shí)現(xiàn)了電梯維修、加入電梯、限制電梯可達(dá)性和限制樓層服務(wù)電梯數(shù)量等功能。在本次作業(yè)中,我也學(xué)會(huì)了一些比較常用的設(shè)計(jì)模式如單例模式和觀察者模式等,以及經(jīng)典的并發(fā)同步模式生產(chǎn)者-消費(fèi)者模式,使得整個(gè)項(xiàng)目的耦合度更低,代碼層次清晰。
第一次作業(yè)
第一次作業(yè)為模擬多線程實(shí)時(shí)電梯系統(tǒng),實(shí)現(xiàn)6部電梯對(duì)實(shí)時(shí)加入的乘客請(qǐng)求做出反應(yīng),接到乘客并送到指定位置,需要模擬電梯的上下行、開(kāi)關(guān)門(mén)以及乘客的進(jìn)出。
架構(gòu)
Producer-Consumer模型
首先分析需要將哪些類(lèi)作為線程運(yùn)行,首先就是輸入(InputHandler)需要不斷讀取請(qǐng)求,其次就是電梯(Elevator)需要不斷反應(yīng)請(qǐng)求并模擬運(yùn)行。
而將這兩個(gè)線程連接起來(lái)的就是請(qǐng)求(Request),所以生產(chǎn)者-消費(fèi)者模型的結(jié)構(gòu)也很清晰了,輸入作為生產(chǎn)者,電梯作為消費(fèi)者,在兩個(gè)之間我們需要一個(gè)容器來(lái)盛放請(qǐng)求,于是設(shè)計(jì)請(qǐng)求隊(duì)列(RequestTable)作為這個(gè)容器,容器有放入請(qǐng)求和取出請(qǐng)求的功能,而之所以容器不作為線程就在于放入和請(qǐng)求這兩個(gè)動(dòng)作的發(fā)出者不是容器本身(參考自助餐窗口,柜臺(tái)是固定的)。
至此,我們可以得到以下的結(jié)構(gòu)
我采用的是所有電梯共用一個(gè)請(qǐng)求隊(duì)列的做法,各個(gè)電梯自由競(jìng)爭(zhēng)。
調(diào)度策略
電梯調(diào)度問(wèn)題沒(méi)有一個(gè)全局最優(yōu)解,總會(huì)有一些情況使得一個(gè)算法劣于其他的算法。目前有的與電梯調(diào)度調(diào)度相關(guān)的算法有ALS,LOOK,SCAN等,課程組給出的算法是ALS,但往屆學(xué)長(zhǎng)大多選擇的是LOOK算法,并且我也感覺(jué)后者實(shí)現(xiàn)的難度要相對(duì)低一點(diǎn),所以選擇了LOOK算法。
具體實(shí)現(xiàn)過(guò)程——
- 首先電梯有一個(gè)初始的運(yùn)動(dòng)方向(建議使用 ± 1 \pm1 ±1,實(shí)現(xiàn)比booolean類(lèi)型方便)
- 判斷是否需要開(kāi)門(mén)
- 電梯內(nèi)是否有人出電梯
- 該樓層是否有人的請(qǐng)求方向和電梯的運(yùn)動(dòng)方向一致
- 判斷電梯內(nèi)是否有人
- 如果有人,那么沿當(dāng)前方向繼續(xù)移動(dòng)一層
- 否則,判斷請(qǐng)求隊(duì)列是否為空
- 如果不為空,接著判斷當(dāng)前方向是否有請(qǐng)求
- 如果有請(qǐng)求,那么沿當(dāng)前方向繼續(xù)移動(dòng)一層
- 否則,變換方向
- 否則,判斷輸入線程是否結(jié)束
- 如果結(jié)束了,那么結(jié)束電梯線程
- 否則,進(jìn)入等待狀態(tài)
- 如果不為空,接著判斷當(dāng)前方向是否有請(qǐng)求
如上所述,電梯做出的反應(yīng)有Open,Move,Reverse,Wait,Over5種,可以設(shè)計(jì)一個(gè)策略類(lèi)(strategy)來(lái)封裝LOOK算法,根據(jù)電梯目前的狀態(tài)給出電梯需要做出的反應(yīng),電梯收到后進(jìn)行相應(yīng)的動(dòng)作。這樣可以使得電梯運(yùn)行和判斷相分離,電梯本身更加專(zhuān)注于動(dòng)作,結(jié)構(gòu)層次也更加清晰。
類(lèi)圖和時(shí)序圖
大致流程如上圖所示,無(wú)論是放入請(qǐng)求還是輸入結(jié)束,都會(huì)使得請(qǐng)求隊(duì)列喚醒電梯,而電梯都會(huì)根據(jù)策略來(lái)進(jìn)行下一步動(dòng)作。
鎖和同步
本次作業(yè)采用的是使用synchronized取得對(duì)象鎖,避免線程安全問(wèn)題,保證同一時(shí)間不會(huì)出現(xiàn)兩個(gè)線程對(duì)同一對(duì)象寫(xiě)或者讀寫(xiě)。
synchronized有三種加鎖的方式——
修飾實(shí)例方法:作用于當(dāng)前對(duì)象實(shí)例加鎖,進(jìn)入同步代碼前要獲得當(dāng)前對(duì)象實(shí)例的鎖
synchronized void method(){}修飾靜態(tài)方法:作用于當(dāng)前類(lèi)的所有實(shí)例,進(jìn)入同步代碼前要獲得當(dāng)前類(lèi)的鎖
synchronized static void method(){}修飾一個(gè)代碼塊:給指定的類(lèi)或者對(duì)象加鎖,在進(jìn)入同步代碼前需要獲得指定類(lèi)或者對(duì)象的鎖
synchronized(obj or example.class){//TODO }修飾實(shí)例方法與在方法內(nèi)修飾整個(gè)代碼塊取得當(dāng)前實(shí)例的鎖類(lèi)似,而修飾靜態(tài)方法與在方法內(nèi)修飾整個(gè)代碼塊取得當(dāng)前類(lèi)的鎖類(lèi)似。
需要注意的是實(shí)例的鎖與類(lèi)的鎖不同,實(shí)例的鎖屬于這個(gè)實(shí)例,類(lèi)的鎖屬于類(lèi)(廢話)。講這個(gè)的目的就是為了說(shuō)明,當(dāng)一個(gè)線程訪問(wèn)加鎖的靜態(tài)方法時(shí),另一個(gè)進(jìn)程依然可以訪問(wèn)加鎖的實(shí)例方法,兩者不沖突。當(dāng)然加鎖的方法和不加鎖的方法也不會(huì)沖突。
需要加鎖的位置就是有多個(gè)線程進(jìn)行讀寫(xiě)的共享數(shù)據(jù),在本次作業(yè)種顯然是請(qǐng)求隊(duì)列,有輸入的寫(xiě),以及電梯的讀寫(xiě),為了保證每次操作的正確性,我們?cè)谶M(jìn)行讀寫(xiě)之前都要對(duì)請(qǐng)求隊(duì)列加鎖,保證進(jìn)行的是原子操作(執(zhí)行過(guò)程不會(huì)被打斷)。本次作業(yè)將請(qǐng)求隊(duì)列內(nèi)部涉及修改和讀取修改變量的方法都加了鎖,在外部對(duì)需要保證請(qǐng)求隊(duì)列狀態(tài)的代碼塊上了鎖。
notifyAll的操作只在修改了共享變量之后存在。
復(fù)雜度分析
主要出在策略類(lèi)中,因?yàn)樯婕按罅康膇f-else判斷以及for循環(huán)遍歷樓層的請(qǐng)求隊(duì)列,所以這部分復(fù)雜度略高。
tips
二次詢問(wèn)
因?yàn)楦鱾€(gè)電梯共用一個(gè)請(qǐng)求隊(duì)列,并且策略類(lèi)將詢問(wèn)和執(zhí)行分開(kāi)了,所以可能會(huì)出現(xiàn)多個(gè)電梯在同一個(gè)樓層都給出開(kāi)門(mén)的指示,但是顯然只有一個(gè)電梯會(huì)接到人(接人操作上鎖,所以不會(huì)出現(xiàn)接到同一個(gè)人),其他的電梯就只開(kāi)門(mén)和關(guān)門(mén),這樣會(huì)白白增加耗電量(雖然自由競(jìng)爭(zhēng)本身會(huì)出現(xiàn)多個(gè)電梯都向一個(gè)請(qǐng)求跑,耗電量巨大)。一種解決方法就是在開(kāi)門(mén)的操作內(nèi)部,先對(duì)請(qǐng)求隊(duì)列上鎖,然后再次詢問(wèn)策略類(lèi)當(dāng)前動(dòng)作是否還應(yīng)該是開(kāi)門(mén),如果是才將人取出,進(jìn)行接下來(lái)的操作。
synchronized (requestTable) {Advice advice = scheduler.getAdvice(curFloor,curNum,direction,eleMap.get(curFloor));if (advice != Advice.OPEN) {return;}tmpQueue = requestTable.take(curFloor,direction,curNum - curQueue.size()); }電梯反轉(zhuǎn)
在電梯沒(méi)人判斷當(dāng)前方向上是否有人的時(shí)候,不應(yīng)該包含當(dāng)前樓層,否則電梯直接沿著當(dāng)前方向走了,會(huì)出現(xiàn)升天或者遁地的情況。
開(kāi)門(mén)判斷
為了簡(jiǎn)便,在策略內(nèi)判斷是否有人進(jìn)入的時(shí)候,傳入的當(dāng)前樓層人數(shù)包括可能下電梯的人,所以可能因?yàn)槌d判斷為不需要開(kāi)門(mén),不過(guò)沒(méi)關(guān)系,因?yàn)槿绻腥嘶叵码娞莸脑捑鸵欢〞?huì)開(kāi)門(mén),在取人的時(shí)候減去下去的人數(shù)即可。(要是沒(méi)有二次詢問(wèn)這個(gè)操作的話,直接按順序下人上人不用特意減,可惜刪不得)
HashMap遍歷刪除
在從請(qǐng)求隊(duì)列中取人的時(shí)候我使用了如下操作
for (PersonRequest person : curQueue) {if (curNum + tmpQueue.size() == Tool.capacity) {break;}if ((person.getToFloor() - curFloor) * direction >= 0) {tmpQueue.add(person);curQueue.remove(person);} }即一邊遍歷一邊刪除,但是運(yùn)行的時(shí)候會(huì)出現(xiàn)java.util.ConcurrentModificationException的報(bào)錯(cuò),于是去網(wǎng)上查找了相關(guān)資料。
簡(jiǎn)單來(lái)說(shuō)就是HashMap內(nèi)部維護(hù)了一個(gè)modCount變量,迭代器里維護(hù)了一個(gè)expectedModCount變量,初始兩者相同,每次HashMap移除和新加元素的時(shí)候modCount會(huì)自增,此時(shí)迭代器里的expectedModCount不變,而迭代器遍歷的時(shí)候會(huì)用到nextNode()方法,當(dāng)兩個(gè)值不等的時(shí)候就會(huì)拋出異常。
基本上JAVA集合類(lèi)在遍歷時(shí)不用迭代器進(jìn)行刪除都會(huì)報(bào)錯(cuò),這樣是為了防止高并發(fā)情況下,多個(gè)線程同時(shí)修改集合導(dǎo)致數(shù)據(jù)不一致。
解決方法就是使用迭代器進(jìn)行遍歷和刪除,迭代器的刪除也會(huì)先判斷兩者值是否相等,然后調(diào)用HashMap的removeNode()方法,最后會(huì)令expectedModCount=modCount,這樣就不會(huì)出現(xiàn)錯(cuò)誤了。
Iterator<PersonRequest> it = curQueue.iterator();while (it.hasNext() && curNum + tmpQueue.size() < Tool.capacity) {PersonRequest person = it.next();if ((person.getToFloor() - curFloor) * direction >= 0) {tmpQueue.add(person);it.remove();} }或者使用線程安全的currentHashMap替代HashMap,或者在遍歷結(jié)束后再遍歷取出來(lái)的列表對(duì)請(qǐng)求隊(duì)列進(jìn)行刪除。
些許優(yōu)化
在研討課時(shí),有同學(xué)提出既然自由競(jìng)爭(zhēng)會(huì)出現(xiàn)1個(gè)請(qǐng)求喚醒6部電梯的情況,那么每來(lái)一個(gè)請(qǐng)求不使用notifyAll而是使用notify就可以減少耗電量,我覺(jué)得有道理,不過(guò)貌似這樣就不像自由競(jìng)爭(zhēng)了,雖然也和直接分配不同,總的來(lái)說(shuō)還是一個(gè)不錯(cuò)的提議。
其次就是另一個(gè)同學(xué)提出在電梯取人的時(shí)候可以不直接鎖整個(gè)隊(duì)列,而是將對(duì)應(yīng)樓層的隊(duì)列鎖住,這樣其他樓層的電梯也可以在此時(shí)取人,雖然優(yōu)化可能不是很明顯,不過(guò)想法很好,顯然要是為了正確性鎖住整個(gè)隊(duì)列是更好的,比較無(wú)腦,為了性能的話就要將需要鎖的部分想清楚,不然正確性可能沒(méi)法保證。
Bug分析
中測(cè)中出現(xiàn)上述有關(guān)HashMap刪除的錯(cuò)誤,強(qiáng)測(cè)和互測(cè)沒(méi)有出現(xiàn)Bug,強(qiáng)測(cè)得分97.1553,還是比較出乎我的意料,畢竟自由競(jìng)爭(zhēng)耗電量確實(shí)難蚌。
第二次作業(yè)
第二次作業(yè)在第一次作業(yè)的基礎(chǔ)上需要模擬電梯系統(tǒng)擴(kuò)建和日常維護(hù)時(shí)乘客的調(diào)度,同時(shí)電梯增加速度和容量參數(shù)。
架構(gòu)
調(diào)度器
為了實(shí)現(xiàn)擴(kuò)建和維護(hù)功能就需要一個(gè)統(tǒng)領(lǐng)所有電梯的容器,來(lái)記錄目前還在運(yùn)行的電梯,于是設(shè)計(jì)了調(diào)度器(Scheduler),同時(shí)由于第一次作業(yè)6部電梯一起搶一個(gè)請(qǐng)求實(shí)在讓我挺難受的,所以我決定讓調(diào)度器同時(shí)擔(dān)任分發(fā)請(qǐng)求的任務(wù),讓每部電梯都有自己的乘客隊(duì)列,于是產(chǎn)生了以下結(jié)構(gòu)
相比于第一次作業(yè),輸入得到請(qǐng)求有三種分別是PersonRequest、ElevatorRequest、MaintainRequest,不同的請(qǐng)求處理方式肯定事不同的,于是在哪個(gè)部分對(duì)請(qǐng)求進(jìn)行分類(lèi)就是一個(gè)問(wèn)題,我采用的方式是在調(diào)度器內(nèi)進(jìn)行分類(lèi)處理,于是輸入和請(qǐng)求隊(duì)列基本上就不需要改動(dòng),然后電梯依賴(lài)的請(qǐng)求隊(duì)列變成了乘客隊(duì)列也只是相當(dāng)于換了個(gè)名字,總的來(lái)說(shuō)原則就是讓每個(gè)類(lèi)的職責(zé)清晰明了。
換乘 + 線程結(jié)束
因?yàn)榫S修請(qǐng)求的存在,電梯可能放出沒(méi)有到達(dá)目的地的乘客,所以會(huì)出現(xiàn)乘客需要換乘的情況,處理還是比較簡(jiǎn)單,直接將沒(méi)有到達(dá)目的地的乘客更改出發(fā)樓層然后重新丟進(jìn)請(qǐng)求隊(duì)列即可,調(diào)度器會(huì)將其作為一個(gè)新的請(qǐng)求讀取并分配。
但是隨之而來(lái)就出現(xiàn)了另一個(gè)問(wèn)題,電梯線程在何時(shí)結(jié)束,之前的結(jié)束判斷是電梯內(nèi)沒(méi)人并且請(qǐng)求隊(duì)列為空且輸入結(jié)束,但是在本次作業(yè)請(qǐng)求來(lái)源不再只是輸入,還可能是換乘的乘客,所以需要改變結(jié)束判斷。
可以發(fā)現(xiàn),雖然有換乘但是總的乘客數(shù)是不變的,于是可以設(shè)計(jì)請(qǐng)求計(jì)數(shù)(RequestCount)維護(hù)一個(gè)count,當(dāng)輸入向請(qǐng)求隊(duì)列放入乘客的時(shí)候令count+1,當(dāng)電梯將乘客送到目的地之后令count-1,于是結(jié)束判斷就變成了輸入結(jié)束且count=0。
由于JAVA沒(méi)有全局變量這個(gè)概念,所以可以使用 單例模式 實(shí)現(xiàn)全局變量,本次作業(yè) 請(qǐng)求隊(duì)列和請(qǐng)求計(jì)數(shù)都使用了單例模式(靜態(tài)變量和方法實(shí)現(xiàn)在本次作業(yè)也可以,不過(guò)兩者略有區(qū)別,在此不做討論)。
有一點(diǎn)需要吐槽的是,課程組給出的PersonRequest不支持修改出發(fā)樓層,于是我自己寫(xiě)了一個(gè)Person,沒(méi)什么不同只是為了修改,當(dāng)然重新實(shí)例化一個(gè)PersonRequest也是可行的。(不過(guò)這個(gè)名字有點(diǎn)長(zhǎng),我不是很喜歡)
類(lèi)圖和時(shí)序圖
復(fù)雜度分析
調(diào)度器的run內(nèi)部根據(jù)請(qǐng)求隊(duì)列的狀態(tài)判斷是結(jié)束線程還是等待還是處理請(qǐng)求,if-else語(yǔ)句導(dǎo)致復(fù)雜度較高。
tips
- 本次作業(yè)中每個(gè)電梯都有自己的乘客隊(duì)列,所以不再需要二次詢問(wèn)。
- 理論課上講解了讀寫(xiě)鎖的使用,能夠允許多個(gè)線程同時(shí)進(jìn)行讀,不過(guò)現(xiàn)在jdk版本下synchronized效率也挺高的,也就沒(méi)進(jìn)行更改。
- 這次作業(yè)要采用自由競(jìng)爭(zhēng)也是可以的,由輸入對(duì)請(qǐng)求分類(lèi)然后進(jìn)行操作即可,不過(guò)架構(gòu)不是很好看就是了,提這個(gè)的目的只是覺(jué)得我的調(diào)度沒(méi)有用到速度這一參數(shù),自由競(jìng)爭(zhēng)在這方面還是有一定優(yōu)勢(shì)。
- 在接到維修指令之后需要立即將電梯從調(diào)度器中刪除,防止有新的請(qǐng)求放入其中。
Bug分析
中測(cè)的時(shí)候出現(xiàn)了以下bug
- 調(diào)度器在請(qǐng)求隊(duì)列中wait,請(qǐng)求計(jì)數(shù)不能直接喚醒,需要先獲得請(qǐng)求隊(duì)列的鎖(我感覺(jué)這一部分我寫(xiě)的比較混亂)
- 因?yàn)槲易约簩?xiě)了Person,但是調(diào)度器在處理的時(shí)候忘記判斷了,導(dǎo)致?lián)Q乘的乘客沒(méi)有處理,雖然我加了assert,但是運(yùn)行的時(shí)候沒(méi)加-ea參數(shù),導(dǎo)致沒(méi)及時(shí)發(fā)現(xiàn)錯(cuò)誤。(值得一提的是課程組貌似也沒(méi)有這個(gè)參數(shù),所以找錯(cuò)還挺麻煩的,不過(guò)這是一個(gè)好習(xí)慣)
強(qiáng)測(cè)沒(méi)有問(wèn)題得分95.1183,互測(cè)出現(xiàn)了程序無(wú)法結(jié)束的問(wèn)題,我找了很久也不知道問(wèn)題出現(xiàn)在哪。
分配策略我采用的是純隨機(jī)(random),運(yùn)氣不好的話可能都分到一部電梯去了。
第三次作業(yè)
第三次作業(yè)在第二次作業(yè)的基礎(chǔ)上,限制了電梯的可達(dá)性(只能在一些樓層開(kāi)門(mén)服務(wù))和樓層的最多服務(wù)電梯數(shù)。
架構(gòu)
這次作業(yè)的架構(gòu)沒(méi)有什么調(diào)整,增加的功能都可以放在已有的模塊中進(jìn)行實(shí)現(xiàn),故UML類(lèi)圖和時(shí)序圖可以參照第二次作業(yè)。
功能實(shí)現(xiàn)
可達(dá)性
雖然規(guī)定了電梯只能在某些樓層開(kāi)門(mén)服務(wù),但在遇到維修的時(shí)候也是可以突破這個(gè)限制的,所以為了統(tǒng)一性我們不應(yīng)該讓可達(dá)性成為電梯的內(nèi)置屬性,而是通過(guò)調(diào)度器分配符合電梯可達(dá)性的乘客,讓其看上去是滿足要求的,實(shí)際上電梯都是功能完全的,能夠在任意樓層開(kāi)門(mén)。
那么問(wèn)題就在調(diào)度器如何進(jìn)行分配了,由于可達(dá)性的存在可能會(huì)出現(xiàn)乘客一趟不能到達(dá)目的地的情況,于是很自然的想到給乘客增加一個(gè)當(dāng)前目的地的屬性,對(duì)于這個(gè)屬性的選取有很多種考量方法,我采用的和課程組類(lèi)似——尋找最少換乘次數(shù)的策略,然后選擇已有請(qǐng)求最少的電梯進(jìn)行分配。
首先在調(diào)度器內(nèi)維護(hù)一個(gè)二維數(shù)組map[i][j]表示有幾部電梯能夠從i樓到j(luò)樓,每次增加或者刪除一個(gè)電梯就根據(jù)它的可達(dá)性對(duì)map進(jìn)行更新
private void updateMap(int access,int type) {for (int i = Tool.minFloor;i <= Tool.maxFloor;i++) {if (accessible(access,i)) {for (int j = Tool.minFloor; j <= Tool.maxFloor;j++) {if (accessible(access,j)) {map[i][j] += type;}}}} }對(duì)于最少換乘次數(shù)dis[i][j]的計(jì)算可以簡(jiǎn)單的使用floyd即可,賦初值部分
for (int i = Tool.minFloor;i <= Tool.maxFloor;i++) {for (int j = Tool.minFloor;j <= Tool.maxFloor;j++) {dis[i][j] = i == j ? 0 : map[i][j] != 0 ? 1 : 114514;} }算完最短路關(guān)于選擇目的地采用map[from][i] != 0 && dis[from][i] + dis[i][to] == dis[from][to]判斷即可,最后會(huì)返回一個(gè)可行的目的地,但是正規(guī)來(lái)講的話應(yīng)該返回一個(gè)集合,這樣后續(xù)分配會(huì)更加均勻。
需要注意的是,設(shè)置當(dāng)前目的地后電梯的策略類(lèi)的判斷同向也應(yīng)該做出相應(yīng)修改,其次每次請(qǐng)求被調(diào)度器取出時(shí)都會(huì)重新規(guī)劃,而不是規(guī)劃出總的路線,因?yàn)槿绻肪€中的電梯被維修了就需要重新規(guī)劃,為了簡(jiǎn)便就每次都算一次。
樓層限制
本次作業(yè)限制一層樓同時(shí)只有4個(gè)服務(wù)(開(kāi)門(mén))的電梯,2個(gè)只接人($set \sube new_set $)的電梯,后者屬于前者,因?yàn)樵谖业膶?shí)現(xiàn)中出去的乘客不會(huì)被馬上接進(jìn)來(lái),所以只接人電梯相當(dāng)于是沒(méi)有出去的人。
Semaphore
實(shí)驗(yàn)課上介紹了Semaphore(信號(hào)量)的使用,鎖的存在使得同一時(shí)間只有一個(gè)線程能夠操作這個(gè)對(duì)象,而如果想要多個(gè)線程同時(shí)使用的話,就需要信號(hào)量了。信號(hào)量?jī)?nèi)部維護(hù)了一個(gè)計(jì)數(shù)器存著可以訪問(wèn)的共享資源的數(shù)量,線程要想訪問(wèn)共享資源就需要獲得信號(hào)量,如果計(jì)數(shù)器大于0則允許訪問(wèn)并將計(jì)數(shù)器-1,如果計(jì)數(shù)器等于0則線程進(jìn)入休眠,當(dāng)某個(gè)線程釋放信號(hào)量之后休眠的進(jìn)程會(huì)被喚醒并嘗試獲取信號(hào)量。
Semaphore semaphore = new Semaphore(10,true); // 信號(hào)量總數(shù),是否公平(先到的先獲得) semaphore.acquire(); // 獲取信號(hào)量 semaphore.release(); // 釋放信號(hào)量獲得信號(hào)量之后一定要釋放。
實(shí)現(xiàn)
有兩種限制,所以對(duì)每層樓都需要兩個(gè)信號(hào)量記為s1(4,true),s2(2,true),當(dāng)電梯需要開(kāi)門(mén)的時(shí)候,如果是普通的電梯只需要獲得s1即可,而只接人電梯則需要先獲得s1再獲得s2(順序好像沒(méi)關(guān)系),關(guān)門(mén)的時(shí)候再釋放相應(yīng)信號(hào)量即可,信號(hào)量都放在了請(qǐng)求計(jì)數(shù)中。
復(fù)雜度分析
getDispatchGoal是求下一個(gè)目的地的部分,內(nèi)部有挺多的循環(huán)嵌套和if-else判斷。
Bug分析
三次測(cè)試都沒(méi)問(wèn)題,強(qiáng)測(cè)得分95.2392,不過(guò)用hhl同學(xué)的評(píng)測(cè)機(jī)總會(huì)是不是出現(xiàn)線程無(wú)法正常結(jié)束的情況,通過(guò)輸出信息發(fā)現(xiàn)是有乘客被吃掉了,放入了電梯但是并沒(méi)有去接他,最后也沒(méi)有發(fā)現(xiàn)是哪出問(wèn)題了。
心得體會(huì)
-
三次作業(yè)電梯運(yùn)行、策略、輸入以及請(qǐng)求隊(duì)列部分原有的代碼基本上都不會(huì)發(fā)生太大改變,主要的變化在于電梯增加維修的能力,調(diào)度器更換調(diào)度策略還有結(jié)束的判斷等,不過(guò)只要大的框架定了后續(xù)的迭代也就比較簡(jiǎn)單了。可以發(fā)現(xiàn)的是后續(xù)迭代課程組對(duì)沒(méi)有調(diào)度器的自由競(jìng)爭(zhēng)并不是很友好,如果不在第二次作業(yè)進(jìn)行重構(gòu)的話,第三次作業(yè)需要改動(dòng)的東西就更多了。不過(guò)架構(gòu)的確定還是蠻看經(jīng)驗(yàn)的,也不能說(shuō)某一個(gè)架構(gòu)就一定好,不過(guò)還是應(yīng)該盡量滿足solid原則。
-
性能方面,我還是為了簡(jiǎn)單和易于實(shí)現(xiàn)起見(jiàn),犧牲了部分性能,調(diào)度采用自由競(jìng)爭(zhēng)、隨機(jī)分配和均勻分配,有的同學(xué)采用影子電梯(復(fù)制所有電梯狀態(tài),模擬判斷請(qǐng)求的最優(yōu)分配方式),聽(tīng)說(shuō)得分挺高的,不過(guò)本來(lái)電梯調(diào)度就沒(méi)有全局最優(yōu),我認(rèn)為首要的還是應(yīng)該保證架構(gòu),有了比較好的架構(gòu),換一個(gè)調(diào)度器也不是很麻煩的事,就像榮文戈老師說(shuō)的,分工好之后要是這個(gè)調(diào)度不行就換個(gè)程序員做調(diào)度器。
-
總的來(lái)說(shuō),相比于第一單元,這一單元感覺(jué)架構(gòu)更加清晰,各個(gè)模塊耦合度不高,功能也相對(duì)清晰,更有面向?qū)ο蟮母杏X(jué)。不過(guò)這次作業(yè)還是留下了一點(diǎn)遺憾,就是第二三次作業(yè)的bug還是沒(méi)找出來(lái),這也讓我感到多線程的艱難。
參考
「BUAA-OO」第二單元:電梯調(diào)度 | Hyggge’s Blog
「BUAA OO Unit 2 HW8」第二單元總結(jié) - 被水淹沒(méi)的一條魚(yú)
HashMap遍歷的時(shí)候使用map.remove會(huì)報(bào)錯(cuò)
為什么 HashMap 不能一邊遍歷一邊刪除?
總結(jié)
以上是生活随笔為你收集整理的[BUAA OO Unit 2 HW8] 第二单元总结的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 12_STM32Cubeide开发_US
- 下一篇: 企业移动互联网营销的最佳切入点在哪里?