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