OO_Unit2_多线程电梯
CSDN博客鏈接
一、第一次作業(yè)
1.需求分析
單部多線程傻瓜調(diào)度(FAFS)電梯
2.實(shí)現(xiàn)方案
輸入接口解析
- 類似于Scanner,我們使用ElevatorInput進(jìn)行阻塞式讀取(第一次作業(yè)較簡單,沒有單獨(dú)開一個(gè)線程,而是直接放在主控類Main中)
- 讀取到null時(shí),表示已經(jīng)讀取完畢,可以退出
- 本接口只會(huì)讀取到正確的請求,錯(cuò)誤的將跳過并在stderr輸出錯(cuò)誤信息(不影響程序本身運(yùn)行,也不會(huì)引發(fā)RUNTIME_ERROR)
- 記得在最后進(jìn)行close()
建立類圖 第一次作業(yè)較為簡單,沒有多考慮,建立了:
- 一個(gè)Elevator線程(實(shí)現(xiàn)run方法)
- 一個(gè)管理請求的隊(duì)列Queue(線程安全,用于管理請求隊(duì)列的ArrayList,這里可以①繼承自ArrayList;②由ArrayList組成;方法②更好,避免了繼承帶來的麻煩問題,并可以將隊(duì)列進(jìn)行封裝:少用繼承,多用組合)
- 其中:細(xì)實(shí)箭頭代表包含關(guān)系,Elevator含有一個(gè)指向Queue的指針,用于訪問Queue中的請求。
- 主控類初始化時(shí)間戳,創(chuàng)建Queue及Elevator后調(diào)用Elevator.start(),最后才開始輸入請求。
實(shí)現(xiàn)思路
主控類主動(dòng)向Queue中輸入請求put,Elevator向Queue中請求拿出請求get,兩者需要互斥:syncronized。
- 在Elevator中,第一次直接使用了輪詢:
- 請求結(jié)束時(shí),Main調(diào)用List中requestEnd方法,Elevator讀取List中End:getEnd
3.測試及修復(fù)
測試思路
此次作業(yè)相對簡單,測試的思路也并不復(fù)雜,只需要按照指導(dǎo)書對每一種不同的省略輸入進(jìn)行測試即可
bug修復(fù)
本次作業(yè)由于使用了輪詢機(jī)制,cpu占用時(shí)間較長,在第二次作業(yè)中修復(fù)此問題
二、第二次作業(yè)
1.需求分析
單部多線程可捎帶調(diào)度(ALS)電梯
2.實(shí)現(xiàn)方案
輸入接口解析
同第一次,只是請求的樓層變成了:電梯樓層:-3層到-1層,1層到16層,共19層
建立類圖
Main建立了Client 線程和 List請求隊(duì)列就結(jié)束了,再由List創(chuàng)建Worker,典型的線程池(Worker Thread)結(jié)構(gòu):
TimableOutput.initStartTimestamp(); List list = new List(); Client client = new Client(list); client.start();如圖,Client Worker各有一個(gè)指向List的指針。而List創(chuàng)造并指向Worker;List類中含有private ArrayList<PersonRequest> list;
實(shí)現(xiàn)思路
Client主動(dòng)向List中輸入請求put,Worker向List中請求拿出請求get,兩者需要互斥:syncronized,改掉了輪詢機(jī)制。- 對于get方法,沒有請求時(shí)需要在原地等待,再由put喚醒。
3.測試及修復(fù)
結(jié)構(gòu)分析
- 復(fù)雜度分析(超標(biāo)及整體)
| Worker.next() | 9 | 6 | 10 |
| Client | 2 | 4 |
| List | 3 | 21 |
| Main | 1 | 1 |
| Worker | 2.67 | 48 |
| xxx | 2.96 | 83 |
依賴關(guān)系分析
ClassCyclicDcyDcy*DptDpt* Client 3 1 3 1 3 List 3 2 3 3 3 Main 3 2 3 2 3 Worker 3 2 3 1 3 從表中得到,各個(gè)類之間耦合關(guān)系在正常范圍內(nèi)。
測試思路
- 本次使用了隨機(jī)自動(dòng)化測試,步驟如下:
- 生成隨機(jī)數(shù)據(jù)
- 實(shí)現(xiàn)定時(shí)輸入(評(píng)測機(jī)的輸入)
- 驗(yàn)證輸出的正確性和與輸入的對應(yīng)
- 將上述操作封裝入批處理進(jìn)行循環(huán)
- 需要將項(xiàng)目打包成.jar文件(有dl同學(xué)寫出了builder腳本:直接通過.zip直接生成.jar文件,形成了如下文件樹)
├──src │ ├─ Archer.jar │ ├─ Berserker.jar │ ├─ Caster.jar | ├─ .... | └─ Alterego.jar ├──lib │ ├─ elevator-input-hw3-1.4-jar-with-dependencies.jar │ └─ timable-output-1.1-raw-jar-with-dependencies.jar └──pat.py - 使用到hdl的黑箱投放已經(jīng)生成好的數(shù)據(jù)
對一些基本條件進(jìn)行檢驗(yàn):
- ①所有乘客都在fromfloor上電梯,并最終到達(dá)tofloor,中途可能有轉(zhuǎn)梯
- ②in,out需要在開關(guān)門之間
- ③電梯連續(xù)地到達(dá)各樓層,相鄰兩層間時(shí)間不少于電梯運(yùn)行時(shí)間
bug修復(fù)
最初使用的方法是當(dāng)且僅當(dāng)電梯為空和輸入請求時(shí)List類將請求放入電梯中,但這樣很可能出現(xiàn)異步的情況:
[0.0]1-FROM-1-TO-15 [0.4]2-FROM-2-TO-14當(dāng)電梯在1樓接到人以后,目的層按15層,可是由于第二個(gè)請求是異步的,沒法正好在電梯到達(dá)2樓前收到消息并挺下來。電梯很有可能走過了第二層才接到第二個(gè)請求。使得2號(hào)乘客在14樓下卻沒有上電梯。
我嘗試著讓List優(yōu)先級(jí)變高,Worker使用yield方法,但都沒有解決線程不安全問題。最后只能讓W(xué)orker沒到達(dá)一層就訪問一次List看看有沒有可以攜帶的請求。
這樣使得線程之間關(guān)系明了,不會(huì)出錯(cuò)了,但是明顯因?yàn)槊繉佣家L問,使得效率降低了,由于處理速度很快,基本上每隔3層才多出10ms,這樣犧牲了小部分性能提升了線程安全性,簡化了邏輯,是很值得的。
三、第三次作業(yè)
1.需求分析
在第二次作業(yè)的基礎(chǔ)上,加入了多個(gè)電梯,并設(shè)置最大允許人數(shù)和允許停靠樓層。
- 電梯數(shù)量:3部,分別編號(hào)為A,B,C
- 電梯可運(yùn)行樓層:-3-1,1-20
- 電梯可停靠樓層:
- A: -3, -2, -1, 1, 15-20
- B: -2, -1, 1, 2, 4-15
- C: 1, 3, 5, 7, 9, 11, 13, 15
- 電梯上升或下降一層的時(shí)間:
- A: 0.4s
- B: 0.5s
- C: 0.6s
- 電梯最大載客量(轎廂容量)
- A:6名乘客
- B:8名乘客
- C:7名乘客
2.實(shí)現(xiàn)方案
建立類圖
本次多線程較為復(fù)雜,按照老師對于Worker Thread的提示,我詳細(xì)看過了《圖解java設(shè)計(jì)模式》的那一章,自己也畫出了一個(gè)大致的類圖:
如圖,細(xì)箭頭表示包含,粗箭頭表示繼承。在第二次作業(yè)的基礎(chǔ)上,運(yùn)用了OCP原則,沒有直接修改Worker類,而是使其繼承了一個(gè)子類NewWorker,在新的類中重寫父類方法,以保證第三次作業(yè)所做的能同時(shí)兼容第二次。
對于PersonRequest也繼承了子類Request,并讓它也成為了一個(gè)線程,并擁有了指向List和Client的指針。
圖中所有的線程包括:Worker, NewWorker, Client, Request
實(shí)現(xiàn)思路
Client主動(dòng)向List中輸入請求put,Worker向List中請求拿出請求get,對于特殊請求Request(不能由一個(gè)電梯完成的)分兩次向電梯發(fā)送請求,三者需要互斥。
其中,Request類比較特殊
- 在Client中判斷此請求是否被接受,不被接受則開啟一個(gè)新的Request線程并設(shè)置一個(gè)原子信號(hào)量sem記錄線程數(shù)量,否則Request就是一個(gè)簡單的PersonRequest
- 在Request類中則查找中間轉(zhuǎn)梯層,并將其切分為兩個(gè)Request,按次序投放:在投放完第一個(gè)后,需要調(diào)用waitFor(request1),直到List和電梯中都不存在request1再投放第二個(gè)。
這樣將Request單獨(dú)開一個(gè)線程,大大簡化了容器類List的設(shè)計(jì),不需要使其成為一個(gè)線程。并且線程之間的交互關(guān)系簡單明了,即使有新的需求:需要換成多次,也能輕松完成。缺點(diǎn)是:當(dāng)這樣換乘的請求過多,使得線程也很多,cpu調(diào)度的效率會(huì)下降。
3.測試及修復(fù)
結(jié)構(gòu)分析
復(fù)雜度分析(超標(biāo)及整體)
| List.hasRequest(Request) | 5 | 3 | 5 |
| Request.run() | 6 | 19 | 19 |
| Worker.moveOne(int,boolean) | 4 | 1 | 4 |
| Worker.next() | 9 | 6 | 10 |
| Client | 2 | 8 |
| List | 3.08 | 40 |
| Main | 1 | 1 |
| NewWorker | 2.55 | 28 |
| Request | 5 | 20 |
| Worker | 2.24 | 56 |
| xxx | 3 | 174 |
可以看到,Request.run()方法三項(xiàng)均超標(biāo)。這一個(gè)run方法具有很強(qiáng)的面向過程特性:
整個(gè)run()方法寫了60行,可以說對各種情況都進(jìn)行了討論并分支。違背了SOLID中的SRP單一職責(zé)原則,更好的方法是對每種特例寫一個(gè)函數(shù),并逐層調(diào)用,使得代碼結(jié)構(gòu)清晰,而且容易修正。這次的2個(gè)bug都是出現(xiàn)在Request.run()方法中的,由此可見,一個(gè)清晰的代碼結(jié)構(gòu)甚至能減少錯(cuò)誤率。
- 依賴關(guān)系分析
| Client | 5 | 2 | 6 | 2 | 5 |
| List | 5 | 4 | 6 | 5 | 5 |
| Main | 5 | 2 | 6 | 4 | 5 |
| NewWorker | 5 | 5 | 6 | 1 | 5 |
| Request | 5 | 4 | 6 | 4 | 5 |
| Worker | 5 | 3 | 6 | 3 | 5 |
從表中得到,各個(gè)類之間耦合關(guān)系依然在正常范圍內(nèi),說明這次結(jié)構(gòu)設(shè)計(jì)上問題不大。
bug修復(fù)
卻忽略了最低層正好是-1,最高層正好是1情形,按上面的算法都到了第0層,出現(xiàn)了數(shù)組轉(zhuǎn)換越界。只好新增了Worker.moveOne(int floor,boolean up)方法,up為true是上移一層,否則下移一層。
int min = Math.min(getFromFloor(), getToFloor()); int max = Math.max(getFromFloor(), getToFloor()); //md,忽略了min和max可能因?yàn)閒rom 與to正好是1,-1而出現(xiàn)0!!!!! min = Worker.moveOne(min, true); max = Worker.moveOne(max, false);轉(zhuǎn)載于:https://www.cnblogs.com/RyanSun17373259/p/10762123.html
總結(jié)
以上是生活随笔為你收集整理的OO_Unit2_多线程电梯的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 四元数研究
- 下一篇: Android GL deadlock