目錄
- 一、設計政策與程式分析
- 第一次作業
- 第二次作業
- 第三次作業
- 二、可擴充性檢查與分析
- 三、bug
- 四、總結與反思
設計思路
輸入,排程器,電梯分别設定成三個線程
排程器采用單例模式
請求資訊封裝為不可變類
電梯設定成具有
Running
,
Opening
Closing
Waiting
四種狀态的狀态機:電梯隻需要根據排程器設定的資訊在四種狀态之間進行切換。
線程通信采用
Lock
&
Condition
的
await
和
signal
方法:相比于
wait
notify
的搭配,同一個
Lock
設定不同的
Condition
,友善對特定線程的喚醒。
排程算法采用
look
:當運作方向上的樓層不再有等待完成的請求且電梯内的請求已經處理完畢時,調轉電梯運作方向。
時序圖:

UML:
LinesCounter:
Metrics:
排程器和電梯類的複雜度較高,未能及時将功能分離
設計思路:
1.大緻思路
兩種線程:主線程和電梯線程
主線程處理輸入并将需求放置到相應的樓層類
電梯作為一個狀态機按照主線程設定的資訊運轉
2.優化&排程政策
①優化:樓層類
把每一樓層當成對象,共19個對象,每個樓層對象包含一個請求隊列。
把主線程當成生産者,電梯當成消費者,樓層類當成托盤(單生産者多消費者模型)
有新需求時,主線程按照新需求的
from
樓層将其放入對應的樓層對象;電梯需要擷取請求時通路與電梯目前樓層對應的樓層對象。
樓層類的好處:相比于把所有新請求放入同一個隊列,樓層類可以保證不同樓層的請求隊列能被不同電梯或生産者同時通路,增加效率,節約時間
19個樓層類和5個電梯實作的道理相同,都是在初始化時重複
new
一個相同的類(樓層類或電梯類),然後建立樓層(或類型)和對象的映射關系,之後的通路也是根據映射關系通路即可。
②排程政策:随機配置設定
當有新需求到來的時候,選擇運作方向和新需求方向相同的電梯,通知此電梯有新的同方向的需求,此處隻是通知而不是将需求和電梯綁定
需求最後由哪個電梯取走完全取決于幾個同方向運作的電梯中哪個先到達需求的
from
樓層(類比現實生活,應該也是哪個電梯先到進入哪個電梯中)
因為不知道需求最後被哪個電梯取走,是以是随機配置設定
随機配置設定的關鍵是不将需求和電梯綁定,直到需求進入電梯的子隊列中,才确定需求由哪個電梯實作
随機配置設定的前提是幾個電梯都是一樣的,無論是電梯可以到達的樓層,還是樓層間運作的速度
随機配置設定的好處是當一個電梯到達一個樓層時,能裝多少就裝多少,提升效率
當然電梯可能出現陪跑的現象,但不增加總時長(因為當他發現同方向已經沒有需求時(被别的電梯裝走了),他就會掉頭或等待)
3.設計中的思考
物盡其能:
比如将人的行為還給人。上下電梯時的輸出,封裝在人的類裡,人需要上下電梯時,直接調用方法即可
傳遞的是對象:
比如排程器需要擷取電梯的若幹資訊,可以将這些資訊封裝成類,這樣排程器擷取時,隻需要先生成一個狀态對象,然後傳遞給排程器。(
return new State(...)
)即可
UML:
雖然将電梯已經拆分lift和liftstate,但複雜度仍然較高,下一次作業進行了進一步拆分
三種線程:主線程、排程器線程和電梯線程
主線程處理輸入并将需求放置到buffer中
排程器線程不斷從buffer中擷取請求,并調用Separator對請求拆分,然後将拆分後的請求放到對應的樓層中
電梯線程不斷從樓層類中獲得請求,并處理請求
2.優化
①樓層類plus:
把每一樓層當成對象,共23個對象。
每個樓層對象包含三個請求隊列:
up
down
wait
up
:存儲目前樓層的向上就緒請求
down
:存儲目前樓層的向下就緒請求
wait
:存儲需要從目前樓層換乘但需要等待前序就緒請求完成的後序等待請求
按照方向以及請求是否就緒進一步細化目前樓層的請求
(讀者如果不知道什麼是樓層類,請看上周推送)
②hang_out
hang_out:比如一個8到-3的請求,需要BA兩類電梯的配合,假設當B尚未将拆分的前序請求送到-2層時,A此時已經空閑,那麼就讓A去-2(某個存在後序等待請求的換乘樓層)提前等着,如果在A去-2的途中來了任務,那就優先執行任務。
3.拆分&排程
①拆分:
核心思想:能直達即直達、無法直達再拆分
直達:一個請求的
from
to
都是某一類電梯的可停靠樓層,則直接将此請求配置設定給此類電梯
直達配置設定順序:
C
A
B
即一個請求
C,A
都能完成,優先配置設定給C。
直達配置設定順序的确立依據:優先級與可停靠樓層數量成反比(即筆者認為C可能會比較閑,是以一個能讓C完成的請求,直接配置設定給C)
如果不能直達,再考慮拆分:
确定換乘樓層:-2,1,5,15(根據能直達即直達的思想确定,即一個從8到-3層的請求,B最遠能到-2,此時再換乘,由A送到-3,其他換乘樓層确定同理)
根據換乘樓層拆分不可直達的請求,将拆分後的請求按照鍊式存儲,即在
Person
請求類中存儲
nextPersonRequest
②排程:
不同類的電梯執行按類配置設定的請求,彼此無競争;同類的電梯互相競争同類的請求
即對于一個電梯:不是你要完成的請求(按類劃分,而不是按
id
配置設定),你别管;是你要完成的請求,如果電梯還沒滿,就把人裝上。
已将電梯分成lift,liftController,liftState三類,有效降低了複雜度
拆分請求的Separator判斷邏輯較多,是以複雜度較高
二、可拓展性檢查與分析
1.按照SOLID原則檢查
- 單一功能原則:第三次作業将電梯分成Lift,LiftController,LiftState三類,各司其職,拆分設定的拆分器Separator,各類功能較單一
- 開閉原則:如果有新的拆分政策,可以繼承拆分器,無需對拆分器本身修改,電梯如果新的狀态需要記錄,同樣繼承LiftState方法,增加屬性、set、get方法等,無需對lift,liftState,liftController修改,滿足了開閉原則
- 裡氏替換原則:程式中未涉及繼承,是以未涉及裡氏替換原則
- 接口隔離原則:程式中未涉及接口,是以未涉及接口隔離原則
- 依賴反轉原則:三類電梯隻依賴同樣的lift類,樓層對象也是複用的相同的樓層類,可以看出能較好的做到抽象,滿足依賴反轉原則
2.可拓展性分析
設計過程中排程器是對從buffer中取出的請求,先拆分然後一次配置設定到位,這種設計有拓展的局限性,如果未來增加動态退出電梯的需求,那麼這種一次到位的配置設定反而增加複雜度。而如果改成每次電梯處理完成一個請求,然後判斷是否為換乘請求,如果是換乘請求,那麼再将請求交由排程器進行配置設定,這樣在未來比如增加動态退出的電梯的需求時,可以減少配置設定的次數,簡化邏輯。
強測與互測的bug為RTLE(real time limit error)
原因在于程式的線程安全問題:電梯最後停不下來
比如如下bug:
@Override
public void run() {
...
while (!end) {
waitForRequest();
execute();
}
...
}
public synchronized void setEnd() {
end = true;
notifyAll();
}
public synchronized void WaitForRequest() {
...
//if (end) {
// return;
//}
...
wait();
...
}
如果不添加
waitForRequest
中注釋的判斷,那麼很有可能在
run
中
while
判斷後此時其他線程調用
setEnd
方法然後再進入
waitForRequest
方法中,那麼電梯可能就會永久的等待下去
雖然自己在完成多線程單元的三次任務之前已經開發過涉及多線程的軟體,但仍出現線程安全bug,主要原因在于盲目自信,因為本地的評測環境不如課程組設定的評測環境,是以在本地測試中并沒有發現這種線程安全bug,在評測過後繼續對回報的出錯資料進行測試,仍然無法複現,這對發現bug和debug十分不利,隻能反複對程式論證,驗證涉及線程通信的各種情況。
在未來的多線程程式的開發中,也可能出現這種無法複現性,不應盲目的依賴測試資料,需要對可能産生線程安全的地方進行反複論證,確定極端情況下,程式也不會crash。
在經曆了0bug的第一單元和若幹bug的第二單元之後,已經能正确了解和看待OO課程的各種制度,感謝在第二單元debug的過程中提供幫助的舍友fty,希望能在未來的單元中戒驕戒躁、穩步前行。
Fighting For Better Future of OO Together!