OO——電梯作業總結

来源:https://www.cnblogs.com/rdwhite/archive/2019/04/23/10755271.html
-Advertisement-
Play Games

[TOC] 電梯作業總結 程式結構與複雜度的分析 第一次作業 1.設計思路 第一次作業是電梯作業的第一次,也是我多線程變成的第一次實踐。任務是編寫一個多線程實時電梯系統,採用FAFS的調度方式。由於第一次作業中沒有涉及到多部電梯以及捎帶的情況,因此來說是比較簡單的。我採用的是指導書提示部分中的模式, ...


目錄

電梯作業總結

程式結構與複雜度的分析

第一次作業

1.設計思路

第一次作業是電梯作業的第一次,也是我多線程變成的第一次實踐。任務是編寫一個多線程實時電梯系統,採用FAFS的調度方式。由於第一次作業中沒有涉及到多部電梯以及捎帶的情況,因此來說是比較簡單的。我採用的是指導書提示部分中的模式,即生產者消費者模式

  • 主線程進行輸入的管理,使用ElevatorInput,負責接收請求並存入隊列
  • 開一個線程,用於模擬電梯的活動,負責從隊列中取出請求併進行執行,執行完後繼續取繼續執行
  • 構建一個隊列,用於管理請求,且需要保證隊列是線程安全的
ElevatorInputHanler

在第五次的作業中,我將主線程main直接作為了輸入管理進程,原因是看到了知道書中的主線程進行輸入的管理,使用ElevatorInput,負責接收請求並存入隊列

Request

在理解了題意和指導書中的模式推薦,我覺得這和我之前在《設計模式——可復用面向對象軟體的基礎》這本書中看到的生產者——消費者模式相近,故我採用了該模式來編寫Request類

Elevator

Elevator類就是電梯類,內部保存著電梯的狀態(如當前樓層),主要完成電梯在接收到請求之後的上下路以及開關門的操作

整體架構圖如下:

2.度量分析

(1)複雜度分析

從圖中可以看出在第一次電梯作業中,我的程式的複雜度在一個合適的範圍,說明我第一次作業的設計是比較合理的

(2)類規模

在第一次作業中我的每個類的規模都在100行以內,比較合理

(3)時序圖

第二次作業

1.設計思路

第二次作業中,電梯仍然是一部電梯,只是演算法上用ALS捎帶演算法代替了FAFS的傻瓜調度,所以整體的架構我還是沿用第一次作業,仍然以生產者消費者模式為主。三各類的功能和第一次作業大體相同,只不過因為ALS演算法而添加了不同的方法,在這裡不過多贅述, 整體結構圖如下:

2.度量分析

(1)複雜度分析

Elevator.deal()函數的代碼如下:

private void deal() throws InterruptedException {
        sleep(30);
        queueRequest = request.getQueueRequest(nowFloor);
        if (first) {
            request.getAnother(direction, nowFloor);
        }
        if (queueRequest.size() >= 1) {
            int i = 0;
            boolean isOpen = false;
            while (i < queueRequest.size()) {
                if ((queueRequest.get(i).getFromFloor() == nowFloor
                        && request.getFlag(i) != 0)
                        || (queueRequest.get(i).getToFloor() == nowFloor
                        && request.getFlag(i) != 1)) {
                    isOpen = true;
                    break;
                }
                i++;
            }
            if (isOpen) {
                open(nowFloor);
            }
            i = 0;
            while (i < queueRequest.size()) {
                if (queueRequest.get(i).getFromFloor() == nowFloor
                        && request.getFlag(i) != 0) {
                    movePeople(nowFloor, queueRequest.get(i).getPersonId(),
                            "IN");
                    if (request.getFlag(i) == 1) {
                        request.setFlag(i, 0);
                    }
                    i++;
                    continue;
                }
                if (queueRequest.get(i).getToFloor() == nowFloor
                        && request.getFlag(i) != 1) {
                    movePeople(nowFloor, queueRequest.get(i).getPersonId(),
                            "OUT");
                    queueRequest.remove(i);
                    request.removeFlag(i);
                    continue;
                }
                i++;
            }
            if (isOpen) {
                sleep(timeOpenOrClose * 2);
                close(nowFloor);
            }
            queueRequest = request.getQueueRequest(nowFloor);
            if (first) {
                request.getAnother(direction, nowFloor);
            }
        }
    }

首先,從代碼中很容易看出裡面包含了很多的if分支以及if分支的嵌套,這導致了v(G)的數值很大;並且在函數內部的request.getQueueRequest()函數是對Request實例方法的調用,並且在其中調用了很多的本類中的方法,增加了模塊與模塊之間的調用,所以iv(G)的數值高;總體的導致整體複雜度ev(G)的提高。

Request.getAnother()的代碼如下:

public synchronized void getAnother(int direction, int nowFloor) {
        if (!queue.isEmpty() && queue.get(0) != null) {
            int i = 0;
            while (i < queue.size()) {
                if (queue.get(i) != null) {
                    if ((queue.get(i).getToFloor() - queue.get(i)
                            .getFromFloor()) * direction > 0
                            && (nowFloor - queue.get(i).getFromFloor())
                            * (nowFloor - queue.get(i).getToFloor()) >= 0) {
                        queueRequest.add(queue.get(i));
                        flag.add(-1);
                        queue.remove(i);
                        continue;
                    }
                }
                i++;
            }
        }
    }

從中可以看車裡面的if分支的條件判斷十分複雜,並且存在嵌套,這大大提高了程式調試的難度,我在第二次作業中出現的BUG也正是因為這個方法中的條件判斷出現了問題。

Request.getQueueRequest()方法的代碼如下:

public synchronized ArrayList<
            PersonRequest> getQueueRequest(int nowFloor) {
        while (queue.isEmpty() && queueRequest.isEmpty()) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        if (queueRequest.isEmpty()) {
            queueRequest.add(queue.get(0));
            flag.add(1);
            if (queue.get(0) != null) {
                queue.remove(0);
            }
        }
        if (queue.size() >= 1 && queue.get(0) != null) {
            for (int i = 0; i < queue.size(); i++) {
                if (queue.get(i) != null) {
                    if ((queueRequest.get(0).getToFloor() - nowFloor)
                            * (queue.get(i).getToFloor() - nowFloor) >= 0) {
                        queueRequest.add(queue.get(i));
                        flag.add(1);
                    }
                }
            }
            for (int i = 1; i < queueRequest.size(); i++) {
                queue.remove(queueRequest.get(i));
            }
        }
        return queueRequest;
    }

從代碼中可以看到,在這個方法中使用了Elevator類中的成員變數nowFloor,並且其中使用了大量的ArrayList類型的成員變數,這大大提高了這個方法的耦合度,導致了Elevator和Request兩個類之間的依賴關係十分顯著。此外,其中大量的if分支嵌套也大大提高了複雜度。

(2)類規模

從圖中可以看出只有*ELevator**類的行數高於100,我覺得其中的原因主要是由於第二次作業是在第一次作業的架構上進行的功能的添加。這導致了Elevator類中由於捎帶增加了很多方法,以及每個方法的代碼量。

(3)時序圖

第三次作業

1.設計思路

在第三次作業中,要求三部電梯進行調度,並且不同的電梯的可達樓層不一樣,這不僅導致了多線程的線程安全問題,也導致了請求的分配問題。因為一個人的請求有可能需要通過換乘來處理。我的程式主要保證的是正確性,所以犧牲了很大一部分的性能。我解決這種情況的方法是,將這種請求分割為兩個請求,以1層為界,因為所有的電梯都可以到達1層。

並且在整體的結構上,我這次採用的是課件上的Worker-Thread模式,相當於對之前的作業進行了重構。

  • Main類是主類
  • Request類是請求類,是對提供的PersonRequest類型的請求的一個封裝,因為考慮到要對請求的起始樓層進行修改
  • ClientThread類是輸入類,負責將接收到的請求存儲到Channel的請求隊列中
  • Channel類是整體的調度器,用來將請求跟據不同的情況分配到不同的電梯中,並且在這次作業中,我採用請求隊列和調度器二合為一的方式
  • WorkerThread類相當於前兩次作業當中的電梯類
    -整體程式的架構圖如下:

2.度量分析

(1)複雜度分析

由於第三次作業中的Channel.getAnother()Channel.takeRequest()WorkerThread.deal()三個方法和第二次作業中的基本相同,故複雜度不在這裡重覆分析。

WorkerThread.moveElevator()方法的代碼如下:

    private void moveElevator(int floor, boolean doDeal)
            throws InterruptedException {
        while (nowFloor != floor) {
            try {
                if (nowFloor < floor) {
                    int temp = nowFloor;
                    for (int i = 1; i <= stopFloor.get(stopFloor.
                            indexOf(temp) + 1) - temp; i++) {
                        if (nowFloor == -1) {
                            nowFloor = 1;
                            i++;
                        } else {
                            nowFloor = temp + i;
                        }
                        sleep(moveTime);
                        arrive(nowFloor);
                    }
                } else {
                    int temp = nowFloor;
                    for (int i = 1; i <= temp - stopFloor.
                            get(stopFloor.indexOf(temp) - 1); i++) {
                        if (nowFloor == 1) {
                            nowFloor = -1;
                            i++;
                        } else {
                            nowFloor = temp - i;
                        }
                        sleep(moveTime);
                        arrive(nowFloor);
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
                if (doDeal && nowFloor != floor) {
                deal();
                if (requestQueue.isEmpty()) {
                    break;
                }
            }
        }
        deal();
    }

從中可以很容易的看出導致ev(G)和v(G)兩個複雜度數值大的主要原因就是內部的if分支的嵌套問題。

WorkerThread.run()

 public void run() {
        while (true) {
            try {
                sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (requestQueue.size() < maxNum) {
                channel.takeRequest(requestQueue,
                        this.getName(), nowFloor, flag, maxNum);
            }
            if (requestQueue.size() == 1 && requestQueue.get(0) == null) {
                break;
            }
            if (requestQueue.isEmpty()) {
                continue;
            }
            try {
                first = true;
                direction = requestQueue.get(0).getFromFloor() - nowFloor;
                logger.debug(direction);
                moveElevator(requestQueue.get(0).getFromFloor(), false);
                first = false;
                Request another;
                while ((another = dealFlag()) != null) {
                    direction = another.getToFloor() - nowFloor;
                    moveElevator(another.getToFloor(), true);
                }
                direction = requestQueue.get(0).getToFloor();
                moveElevator(requestQueue.get(0).getToFloor(), true);
                while (!requestQueue.isEmpty() && requestQueue.get(0) != null) {
                    if (flag.get(0) == 1) {
                        direction = requestQueue.get(0).getFromFloor();
                        moveElevator(requestQueue.get(0).getFromFloor(), true);
                    } else {
                        direction = requestQueue.get(0).getFromFloor();
                        moveElevator(requestQueue.get(0).getToFloor(), true);
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

與前面的moveELevator()相比,除了因為if分支導致的結構複雜以外,這個函數中由於調用了Channel類的實例channel的方法,導致了兩個類之間的調用關係複雜,依賴程度高,提高了程式的耦合度,而且作為一個線程的run()方法,這樣的高複雜度很容易導致線程安全的問題

(2)類規模

(3)時序圖

程式BUG的分析

在這三次作業的強測和互測階段,我一共只遇到了一種BUG。這個BUG體現在輸出中就是一個人還沒有進電梯就從電梯裡面出來了。我遇到這個BUG是在第二次電梯作業的強測階段,一共有4個測試用例測出了我這個BUG,導致我自己差一點沒進強測。

經過分析,我發現這個BUG出現的原因是在捎帶請求的處理上,出問題的地方是Request類中:

public synchronized void getAnother(int direction, int nowFloor) {
        if (!queue.isEmpty() && queue.get(0) != null) {
            int i = 0;
            while (i < queue.size()) {
                if (queue.get(i) != null) {
                    if ((queue.get(i).getToFloor() -
                            queue.get(i).getFromFloor()) * direction >= 0
                            && (queue.get(i).getFromFloor() - nowFloor)  /***/
                            * direction >= 0 /***/
                            && (queue.get(i).getToFloor() - nowFloor) /***/
                            * direction >= 0 /***/
                            && direction != 0) { /***/
                        requestQueue.add(queue.get(i));
                        flag.add(-1);
                        queue.remove(i);
                        continue;
                    }
                }
                i++;
            }
        }
    }

出問題的部分就是我在代碼中標註出來的。這一部分的條件一開始我沒有加上。我處理捎帶請求的思路是,我的電梯剛開始向著主請求的起始樓層運動,在這個過程中,如果有一個請求的運行方向和我當前電梯運行方向一樣,我就捎帶。但是,我最初沒有加上一個判斷條件:這個捎帶請求的起始樓層必須是還未到達。這就導致了我實際上在隊列中存儲了一個請求作為捎帶請求,但是沒有輸出這個人進入電梯的消息,最終導致了BUG的產生。

互測

自動評測

在這三次的互測過程中,我採用的都是自動化評測的方式。

我的測評機主要分為一下幾個部分:

  • 數據生成器
  • 定時輸入數據
  • 結果檢查

數據生成器

  • N:請求總數,通過randint聲稱
  • personId:人員Id,從0開始遞增
  • Floor:一個列表,裡面存儲著電梯能夠到達的樓層
  • Time:時間,起始值為0.0
  • TimeAdd:一個列表,Time的增長幅度,初值為[0.0, 0.1]

每一次迴圈,使用隨機數來選擇0.0或0.1作為時間的增長

用隨機數來選擇起始樓層和終 止樓層,如果選擇的樓層一樣則重新選擇直到不同為止
將請求輸出到文件中

定時輸入數據(JAVA)

  • DealClass
  • InputClass
  • Instruction
  • MainClass
  • OutputClass
InputClass

返回一個String[]數組,內部保存著所有的輸入

Instruction
  • 指令類
  • 內部有time(double)和ins(String)兩個屬性
  • time屬性是請求的時間
  • Ins屬性是請求
  • 構造方法
Public Instruction(double time, String ins) {
    this.time = time;
    this.ins = ins;
}
DealClass
  • 解析輸入的請求
  • 通過正則表達式提取請求中的時間和請求本身
  • 使用instruction的構造方法
  • 返回一個Instruction[]數組
OutputClass
  • 輸出線程
  • 當前時間now初始為0
  • 對於每一個請求,通過sleep的方式控制輸出的前後
Thread.sleep((long)(time * (ins[i].getTime() - now)));
Now = instruction[i].getTime();

結果檢查(python)

  • 初始化一個trueRequest字典和outRequest字典
  • trueRequest通過解析輸入文件,得到每個人的真正請求
  • outRequest通過解析輸出文件,得到每個人在電梯運行之後實際執行的請求
  • PersonId作為鍵值,[fromFloor, toFloor]作為元素,通過對比字典的每一項,如果相同就說明結果正確

Batch命令

javac -cp elevator-input-hw3-1.4-jar-with-dependencies.jar;timable-output-1.1-raw-jar-with-dependencies.jar src/*.java -d src/class
javac -cp elevator-input-hw3-1.4-jar-with-dependencies.jar;timable-output-1.1-raw-jar-with-dependencies.jar TestClass/src/*.java -d TestClass/class
set /a a = 0
:start
echo %a%
data.py
type in.txt |java -classpath TestClass/class MainClass |java -classpath elevator-input-hw3-1.4-jar-with-dependencies.jar;timable-output-1.1-raw-jar-with-dependencies.jar;src/class MainClass > dzh.txt
check.py
if %ERRORLEVEL% == 0 (
    color 47
    goto end
    ) else (
    color 27
    set /a a += 1
    goto start
)
:end

結果展示

有效性

與第一次作業不同,我認為在本次評測過程中,自動化評測是十分必要的。在第一次作業裡面,如果你不會自動評測,通過手動構造樣例最起碼是能夠評測的,但是這次作業如果沒有自動化的方式,光定時輸入一點就很難做到。要想能夠模擬評測的機的定時輸入方式,必須要採用自動化評測的方式才夠準確。如果是單純的靠手輸入,是難以保障準確性的。

但是呢,我認為線程安全方面的BUG,閱讀代碼要比自動評測更有效。因為通過代碼結構的分析,你就能找到這個程式裡面哪些資源是互斥的,哪些線程之間需要同步關係,一旦你找到了其中的BUG,那麼只需要簡單的構造一組樣例就可以hack別人。

總結

本次的三次電梯作業,讓我對多線程有了一個從無到有的實踐。相比第一次作業,我的程式的設計明顯的有了提高,在這三次作業中,我使用過兩個模式:生產者模式Worker-Thread模式,這使得我的程式的模塊的分工分明,大大降低了BUG修複過程中的困難。並且,在電梯作業的設計過程中,我對請求隊列究竟放在電梯內部還是放在調度器中思考了很久,最終選擇了放在電梯中。我認為這樣的思考是一個很好的鍛煉過程。

並且在這三次作業中我嘗試了自己製作對拍器,也對batch命令有了更深的認識。

但是,在這三次電梯作業中我都沒有去嘗試對電梯的調度進行優化,在研討課上聽了大佬們的LOOK演算法,覺得這一部分的缺漏是一個很大的需要補的地方。並且,對於多線程裡面鎖的使用,我都是直接將方法上了鎖,而沒有採用鎖代碼塊或者採用阻塞隊列這樣更高效率並且更高端的操作,我認為這些都是在以後需要提高的。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 項目中經常遇到要選擇城市。用到三級聯動的方式 微信小程式的 組件 是三級聯動的,但是無法自定義,這讓我們心痛不已,值得我們欣慰的 picker view 組件是可以自定義添加多個選項,但還是無法聯動。既然這樣那就自己寫一個聯動。 做到如下圖所示: 分為動態獲取地址 引用靜態文件獲取地址 <! mor ...
  • 轉自:https://www.cnblogs.com/kidsitcn/p/7182274.html 比例尺函數是這樣的javascript函數: 接收通常是數字,日期,類別等data輸入並且: 返回一個代表可視化元素的值,比如坐標,顏色,長度或者半徑等 比例尺通常用於變換(或者說映射)抽象的數據值 ...
  • 做法就是使用iframe標簽 1.text,pdf的文件預覽 <iframe class="filename" :src="文件的地址" width='100%' height='600' frameborder='1' ></iframe> 2.doc,xls,ppt等office的預覽 <ifr ...
  • 一、對象的擴展 1.1對象屬性名錶達式 ES6可以在JSON中使用[]包裹一個key的名字。此時這個key將用表達式作為屬性名(被當做變數求值),這個key值必須是字元串。 1.2 Object.assign()方法 該方法用於對象的合併,將源對象的所有可枚舉的屬性,複製到目標對象。 Object. ...
  • 譯者按: 為什麼偏要用 符號? 原文 : "JavaScript's new private class fields" 譯者 : "Fundebug" 本文采用意譯,版權歸原作者所有 "proposal class fields" 與 "proposal private methods" 定義了 ...
  • 文章首發: "結構型模式:組合模式" 七大結構型模式之三:組合模式。 簡介 姓名 :組合模式 英文名 :Composite Pattern 價值觀 :專門解決各種樹形疑難雜症 個人介紹 : Compose objects into tree structures to represent part ...
  • 1 三次作業的設計策略 經過了上一單元的訓練,我也積累了一些設計策略上的經驗。在這一單元的一開始,我便儘可能地把問題中的各個功能實體區分開來,分別封裝成類,以便於隨後作業中新需求的加入。與此同時,我也在有意地控制住方法的規模,依照程式邏輯層次化地設計方法,使得每個方法都不至於過分臃腫,從而增加代碼的 ...
  • 1. " java爬蟲系列第一講 爬蟲入門(爬取動作片列表)" 2. " java爬蟲系列第二講 爬取最新動作電影《海王》迅雷下載地址" 3. " java爬蟲系列第三講 獲取頁面中絕對路徑的各種方法" 4. " java爬蟲系列第四講 採集"極客時間"專欄文章、視頻專輯" 5. "java爬蟲系列 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...