日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

OO_Unit2_多线程电梯

發(fā)布時間:2025/5/22 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 OO_Unit2_多线程电梯 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

CSDN博客鏈接

一、第一次作業(yè)

1.需求分析

單部多線程傻瓜調(diào)度(FAFS)電梯

2.實(shí)現(xiàn)方案

輸入接口解析

  • 類似于Scanner,我們使用ElevatorInput進(jìn)行阻塞式讀取(第一次作業(yè)較簡單,沒有單獨(dú)開一個線程,而是直接放在主控類Main中)
  • 讀取到null時,表示已經(jīng)讀取完畢,可以退出
  • 本接口只會讀取到正確的請求,錯誤的將跳過并在stderr輸出錯誤信息(不影響程序本身運(yùn)行,也不會引發(fā)RUNTIME_ERROR)
  • 記得在最后進(jìn)行close()
while (true) {PersonRequest request = elevatorInput.nextPersonRequest();if (request == null) {break;} else {queue.add(request);} } elevatorInput.close();

建立類圖 第一次作業(yè)較為簡單,沒有多考慮,建立了:

  • 一個Elevator線程(實(shí)現(xiàn)run方法)
  • 一個管理請求的隊列Queue(線程安全,用于管理請求隊列的ArrayList,這里可以①繼承自ArrayList;②由ArrayList組成;方法②更好,避免了繼承帶來的麻煩問題,并可以將隊列進(jìn)行封裝:少用繼承,多用組合)
  • 其中:細(xì)實(shí)箭頭代表包含關(guān)系,Elevator含有一個指向Queue的指針,用于訪問Queue中的請求。
  • 主控類初始化時間戳,創(chuàng)建Queue及Elevator后調(diào)用Elevator.start(),最后才開始輸入請求。

實(shí)現(xiàn)思路

主控類主動向Queue中輸入請求put,Elevator向Queue中請求拿出請求get,兩者需要互斥:syncronized。

  • 在Elevator中,第一次直接使用了輪詢:
while (!getEnd() || queue.hasNext()) {try {request = queue.getRequest();...} catch (Exception e1) {stay(circleTime);// 每隔circleTime判斷一次請求} }
  • 請求結(jié)束時,Main調(diào)用List中requestEnd方法,Elevator讀取List中End:getEnd
//List里方法,Main和Elevator分別訪問,需要互斥;在調(diào)用requestEnd方法結(jié)束時需要notify synchronized void requestEnd() {end = true;notifyAll(); }synchronized boolean getEnd() {return end && list.isEmpty(); }

3.測試及修復(fù)

測試思路

此次作業(yè)相對簡單,測試的思路也并不復(fù)雜,只需要按照指導(dǎo)書對每一種不同的省略輸入進(jìn)行測試即可

bug修復(fù)

本次作業(yè)由于使用了輪詢機(jī)制,cpu占用時間較長,在第二次作業(yè)中修復(fù)此問題

二、第二次作業(yè)

1.需求分析

單部多線程可捎帶調(diào)度(ALS)電梯

2.實(shí)現(xiàn)方案

輸入接口解析

同第一次,只是請求的樓層變成了:電梯樓層:-3層到-1層,1層到16層,共19層

建立類圖

Main建立了Client 線程和 List請求隊列就結(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各有一個指向List的指針。而List創(chuàng)造并指向Worker;List類中含有private ArrayList<PersonRequest> list;

    實(shí)現(xiàn)思路
    Client主動向List中輸入請求put,Worker向List中請求拿出請求get,兩者需要互斥:syncronized,改掉了輪詢機(jī)制。
  • 對于get方法,沒有請求時需要在原地等待,再由put喚醒。
synchronized boolean get() {while (list.isEmpty()) {try {if (end) {return false;//等待之前判斷是否結(jié)束了請求}wait();} catch (InterruptedException e) {e.printStackTrace();}}getOne();//直接分配一個請求;前提:list非空getAnother();//在已有的隊列中尋求其他請求,并放入Workerreturn true; }

3.測試及修復(fù)

結(jié)構(gòu)分析

  • 復(fù)雜度分析(超標(biāo)及整體)
Methodev(G)iv(G)v(G)
Worker.next()9610
ClassOCavgWMC
Client24
List321
Main11
Worker2.6748
Packagev(G)avgv(G)tot
xxx2.9683
- 如表,Worker.next的ev(G)和v(G)高:- 基本復(fù)雜度:來衡量程序**非結(jié)構(gòu)化程度**的;高意味著**非結(jié)構(gòu)化程度高**,難以模塊化和維護(hù)- 圈復(fù)雜度:用來衡量一個模塊判定**結(jié)構(gòu)的復(fù)雜程度**,數(shù)量上表現(xiàn)為獨(dú)立路徑的條數(shù);圈復(fù)雜度大說明程序代碼可能**質(zhì)量低且難于測試和維護(hù)**- 模塊設(shè)計復(fù)雜度:模塊設(shè)計復(fù)雜度是用來衡量模塊判定結(jié)構(gòu),即模塊和其他模塊的調(diào)用關(guān)系。意味模塊**耦合度高**,這將導(dǎo)致模塊**難于隔離、維護(hù)和復(fù)用**。```private int next() {int i;if (up) {for (i = minFloor; i <= maxFloor; i++) {if (i != 0) {if (getStop(i)) {break;}}}} else {for (i = maxFloor; i >= minFloor; i--) {if (i != 0) {if (getStop(i)) {break;}}}}if (i > maxFloor || i < minFloor) {return -999;}return i;}```- 這段用于尋找下一個停靠樓層的代碼希望在上升時由低到高遍歷,下降時由高到低遍歷。- 寫的很丑,結(jié)構(gòu)層次化差,分支條件多。很容易出錯,也很難發(fā)現(xiàn)錯誤的地方。- 異常時草率地返回了一個`-999`不太負(fù)責(zé),應(yīng)該直接拋出異常`throw new IllegalStateException("No Stop");`以后可以更好地發(fā)現(xiàn)錯誤。 - 其余的數(shù)據(jù)在正常范圍之內(nèi)
  • 依賴關(guān)系分析

    ClassCyclicDcyDcy*DptDpt*
    Client31313
    List32333
    Main32323
    Worker32313

    從表中得到,各個類之間耦合關(guān)系在正常范圍內(nèi)。

測試思路

  • 本次使用了隨機(jī)自動化測試,步驟如下:
    • 生成隨機(jī)數(shù)據(jù)
    • 實(shí)現(xiàn)定時輸入(評測機(jī)的輸入)
    • 驗證輸出的正確性和與輸入的對應(yīng)
    • 將上述操作封裝入批處理進(jìn)行循環(huán)
  • 利用 python 的 random 庫生成若干條隨機(jī)數(shù)據(jù)
  • # make.py t = round(random.uniform(0,100),1) #生成[0,100)間的隨機(jī)小數(shù)并保留一位小數(shù) ID = random.randint(0,99999999) fromfloor= random.randint(-3,20) tofloor = random.randint(-3,20) println("[%.1f]%d-FROM-%d-TO-%d" %(t,ID,fromfloor,tofloor))
  • 利用黑箱投放數(shù)據(jù)
    • 需要將項目打包成.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)行檢驗:
    • ①所有乘客都在fromfloor上電梯,并最終到達(dá)tofloor,中途可能有轉(zhuǎn)梯
    • ②in,out需要在開關(guān)門之間
    • ③電梯連續(xù)地到達(dá)各樓層,相鄰兩層間時間不少于電梯運(yùn)行時間
  • shell 腳本運(yùn)行批處理文件
  • python make.py > make.txt java -jar my.jar < make.txt > result.txt cat python judge.py < result.txt

    bug修復(fù)

    最初使用的方法是當(dāng)且僅當(dāng)電梯為空輸入請求時List類將請求放入電梯中,但這樣很可能出現(xiàn)異步的情況:

    [0.0]1-FROM-1-TO-15 [0.4]2-FROM-2-TO-14

    當(dāng)電梯在1樓接到人以后,目的層按15層,可是由于第二個請求是異步的,沒法正好在電梯到達(dá)2樓前收到消息并挺下來。電梯很有可能走過了第二層才接到第二個請求。使得2號乘客在14樓下卻沒有上電梯。

    我嘗試著讓List優(yōu)先級變高,Worker使用yield方法,但都沒有解決線程不安全問題。最后只能讓W(xué)orker沒到達(dá)一層就訪問一次List看看有沒有可以攜帶的請求。

    這樣使得線程之間關(guān)系明了,不會出錯了,但是明顯因為每層都要訪問,使得效率降低了,由于處理速度很快,基本上每隔3層才多出10ms,這樣犧牲了小部分性能提升了線程安全性,簡化了邏輯,是很值得的。

    三、第三次作業(yè)

    1.需求分析

    在第二次作業(yè)的基礎(chǔ)上,加入了多個電梯,并設(shè)置最大允許人數(shù)和允許停靠樓層。

    • 電梯數(shù)量:3部,分別編號為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
    • 電梯上升或下降一層的時間:
      • 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è)計模式》的那一章,自己也畫出了一個大致的類圖:

    如圖,細(xì)箭頭表示包含,粗箭頭表示繼承。在第二次作業(yè)的基礎(chǔ)上,運(yùn)用了OCP原則,沒有直接修改Worker類,而是使其繼承了一個子類NewWorker,在新的類中重寫父類方法,以保證第三次作業(yè)所做的能同時兼容第二次。

    對于PersonRequest也繼承了子類Request,并讓它也成為了一個線程,并擁有了指向List和Client的指針。

    圖中所有的線程包括:Worker, NewWorker, Client, Request

    實(shí)現(xiàn)思路

    Client主動向List中輸入請求put,Worker向List中請求拿出請求get,對于特殊請求Request(不能由一個電梯完成的)分兩次向電梯發(fā)送請求,三者需要互斥。

    其中,Request類比較特殊

    • 在Client中判斷此請求是否被接受,不被接受則開啟一個新的Request線程并設(shè)置一個原子信號量sem記錄線程數(shù)量,否則Request就是一個簡單的PersonRequest
    PersonRequest a = elevatorInput.nextPersonRequest();if (a == null) {break;}Request request = new Request(a.getFromFloor(),a.getToFloor(), a.getPersonId(), list, this);if (list.accept(request)) {list.put(request);} else {sem.getAndIncrement();new Thread(request).run();}
    • 在Request類中則查找中間轉(zhuǎn)梯層,并將其切分為兩個Request,按次序投放:在投放完第一個后,需要調(diào)用waitFor(request1),直到List和電梯中都不存在request1再投放第二個。
    Request request1 = new Request(getFromFloor(), i,getPersonId(), list, client); Request request2 = new Request(i, getToFloor(),getPersonId(), list, client); list.put(request1); list.waitFor(request1); list.put(request2); client.decrease(); client.finishAndNotify();
    • 這樣將Request單獨(dú)開一個線程,大大簡化了容器類List的設(shè)計,不需要使其成為一個線程。并且線程之間的交互關(guān)系簡單明了,即使有新的需求:需要換成多次,也能輕松完成。缺點(diǎn)是:當(dāng)這樣換乘的請求過多,使得線程也很多,cpu調(diào)度的效率會下降。

      3.測試及修復(fù)

      結(jié)構(gòu)分析

    • 復(fù)雜度分析(超標(biāo)及整體)

    Methodev(G)iv(G)v(G)
    List.hasRequest(Request)535
    Request.run()61919
    Worker.moveOne(int,boolean)414
    Worker.next()9610
    ClassOCavgWMC
    Client28
    List3.0840
    Main11
    NewWorker2.5528
    Request520
    Worker2.2456
    Packagev(G)avgv(G)tot
    xxx3174

    可以看到,Request.run()方法三項均超標(biāo)。這一個run方法具有很強(qiáng)的面向過程特性:

    整個run()方法寫了60行,可以說對各種情況都進(jìn)行了討論并分支。違背了SOLID中的SRP單一職責(zé)原則,更好的方法是對每種特例寫一個函數(shù),并逐層調(diào)用,使得代碼結(jié)構(gòu)清晰,而且容易修正。這次的2個bug都是出現(xiàn)在Request.run()方法中的,由此可見,一個清晰的代碼結(jié)構(gòu)甚至能減少錯誤率。

    • 依賴關(guān)系分析
    ClassCyclicDcyDcy*DptDpt*
    Client52625
    List54655
    Main52645
    NewWorker55615
    Request54645
    Worker53635

    從表中得到,各個類之間耦合關(guān)系依然在正常范圍內(nèi),說明這次結(jié)構(gòu)設(shè)計上問題不大。

    bug修復(fù)

  • 第一個bug出現(xiàn)在Request.run()中:本來希望min和max依次代表請求最低層上一層、最高層下一層:
  • int min = Math.min(getFromFloor(), getToFloor()) + 1; int max = Math.max(getFromFloor(), getToFloor()) - 1;

    卻忽略了最低層正好是-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可能因為from 與to正好是1,-1而出現(xiàn)0!!!!! min = Worker.moveOne(min, true); max = Worker.moveOne(max, false);
  • 第二個bug也出現(xiàn)在Request.run()中,對于奇怪的請求:3->4,3->2,原本在兩層之間找換乘失效了,而我正好沒有考慮到這種情況:
  • //臨界情況 if (min > max) {if (getFromFloor() == 3) {if (getToFloor() == 4) {dispatch(5); //3->4拆解成3->5->4} else if (getToFloor() == 2) {dispatch(1); //3->4拆解成3->1->2}} }

    轉(zhuǎn)載于:https://www.cnblogs.com/RyanSun17373259/p/10762123.html

    總結(jié)

    以上是生活随笔為你收集整理的OO_Unit2_多线程电梯的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。