OO_第二單元總結

第二次總結博客(電梯單元)

16071070 陳澤寅 2019.4.20

一:多線程實驗初感

這個單元是多線程設計的實踐單元,主要讓咱們運用多線程的原理與思想去完成一個模擬電梯運行的策略。從最開始的單步電梯的傻瓜式調度,到第二次做業的單步電梯的捎帶式策略,再到第三次做業的多部電梯捎帶式運行策略。一次次的難度增強,也讓咱們發現了多線程的使用規則和方法,而且在一次次的bug中更加體會到了鎖的機制,以及各類併發機智的使用規則。雖然仍是有不少的問題,可是從這個單元確實學到了不少東西。java

二:3次單元做業的設計策略

第五次編程做業(單線程傻瓜電梯)

這個單元編程做業,我設計了兩個線程類來完成所需的請求。分別爲:輸入請求類也就是咱們的生產者類,還有一個線程就是咱們的電梯,也就是消費者類。兩個線程共享一個請求隊列。輸入請求線程是非阻塞的,它負責向請求隊列裏寫入請求,而電梯線程負責從請求隊列裏讀出請求。並執行相應的請求。python

相應的類圖以下。linux

裏面一共有三個類,其中Quene是咱們的共享隊列,是一個資源,供InputElevator線程去爭奪。所以Quene裏的方法都應該寫成同步方法,這樣才能避免線程不安全的狀況發生。時序邏輯以下。git

輸入線程不斷地向Quene裏寫入請求,Elevator不斷地從Quene裏取出請求而且執行。當Input讀入null結束請求時,便向Elevator發出一個結束信號,此時Input線程結束。Elevator線程收到這個結束信號時,判斷Quene是否爲空,不爲空則繼續取出任務執行完。直到Quene也爲空時則 Elevator線程也結束並退出。shell

  • 第五次做業代碼分析報告:
sourceFile TotalLine
Dispatcher.java 3
Elevator.java 99
Input.java 53
Quene.java 44

度量分析:編程

method ev(G) iv(G) v(G)
Elevator.close() 1.0 2.0 2.0
Elevator.Elevator(Quene) 1.0 1.0 1.0
Elevator.go(int) 1.0 2.0 2.0
Elevator.in(int) 1.0 2.0 2.0
Elevator.open() 1.0 1.0 1.0
Elevator.out(int) 1.0 2.0 2.0
Elevator.run() 4.0 5.0 6.0
Input.getFlag() 1.0 1.0 1.0
Input.Input(Quene) 1.0 1.0 1.0
Input.main(String[]) 1.0 1.0 1.0
Input.run() 3.0 3.0 4.0
Quene.getList() 1.0 1.0 1.0
Quene.getNum() 1.0 1.0 1.0
Quene.requestGet() 1.0 2.0 2.0
Quene.write(PersonRequest) 1.0 1.0 1.0
Total 20.0 26.0 28.0
Average 1.33 1.73 1.87
  • 代碼分析:數組

    咱們能夠看到此次代碼中,ELevator.run的複雜度較高,這是由於在第一次多線程的過程當中沒可以把代碼模塊化,致使電梯在運行的過程當中還承擔了不少本不該該電梯來作的事情。其實在第一次做業中,因爲沒有涉及調度器,此時電梯還須要同時扮演調度起的角色,所以其複雜度會比較高。安全

  • 代碼缺陷:數據結構

    第一次多線程實驗中,尚未理解到線程wait()notify()的好處與做用,所以並無使用等待喚醒機制,致使電梯進程一直在輪詢判斷Quene裏到底有沒有新來的請求,這也致使當請求隊列位空,可是輸入進程尚未結束的時候,電梯線程一直在作無謂的動做,佔據了大量的CPU時間,這是一個很愚蠢的設計,解決方法就是引入了等待喚醒機制,具體實施在下一次實驗中。多線程

  • 關於Solid原則

    此次的做業沒有很好的遵循SOLID原則,首先電梯線程參與了向Quene索要請求的事情,違反了SRP原則。而電梯應該只需管本身的運行,應該把請求的分配交給Dispatcher線程來管。同時也沒有很好的踐行ISP原則,沒有實現接口。

第六次編程做業(多線程捎帶電梯)

在第六次編程中雖然仍是一部電梯,可是引入了捎帶的策略。我吸收上一次編程中違反SOLID的教訓,將Elevator線程與調度分開,從新設計了一個Dispatcher調度器線程,其做用是從Quene中獲取請求,而且根據Elevator的運行狀態將其分配給電梯便可。而此次的電梯有一個主請求,和一個捎帶請求隊列。電梯無論怎麼得到請求,它只管按照本身的請求去執行上樓下樓,上客下客的操做。請求的分配只歸Dispatcher來管。
電梯的數據結構以下。

private volatile int floor = 1; //初始化在1層
private volatile PersonRequest mainRequset = null;//主請求
private volatile Vector<Person
            subRequest = new Vector<Person();
private volatile int empty = 1;//主請求是否爲空的標誌
private volatile int inside = 0;
private volatile int status = 1;//0 means down,1 means up

其中每一個電梯都有一個mainRequest,和一個Vector類型的SubRequest,而且每一個電梯會返回給調度器他的當前樓層,他的運行方向等信息,來讓Dispatcher完成新的調度。

類圖

能夠看到最大的變化就是增長了一個Dispatcher調度器類,這個類鏈接了Elevator線程和Input線程。它從Input中獲取請求,而後分配給Elevator

時序圖

  • 1: Input線程和Dispatcher之間的時序關係

從上圖可知,Input每次來消息,寫入到Quene中去,而後會向Dispatcher發起一個notify信號喚醒正在等待的調度器,告訴它有新的任務來了,他能夠恢復調度。而後DispatcherQuene中去取心得請求,把它放到本身的隊列中,並完成與Elevator的交互。以後若是沒有其餘新的請求,則它wait()。等待心得喚醒信號。

  • 2:Dispatcher線程和Elevator線程的時序關係圖。

Elevator線程當本身主請求爲空的時候,就進行wait(),等待調度器線程給其分派請求。Dispcther給其分派請求時,notify()這個電梯,其執行本身的主請求和副請求,一旦主請求結束,而且沒副請求時,電梯notify()調度器,讓它給本身分配新的請求。

  • 3:結束判斷標誌

1:Input結束標誌爲讀到null

2:Dispatcher結束標誌爲Input結束而且Quene以及本身的請求隊列所有分配完成爲空時。

3:Elevator結束標誌爲Dispacther結束而且本身當前的主請求以及副請求所有執行完畢。

代碼分析報告

代碼規模

Source File Total Lines Source CodeLines
Clock.java 3 3
Dispatcher.java 137 111
Elevator.java 269 233
Input.java 87 69
Person.java 30 20
Quene.java 51 33
Total 577 469

代碼複雜度分析

method ev(G) iv(G) v(G)
Dispatcher.check() 1.0 6.0 6.0
Dispatcher.Dispatcher(Elevator,Quene) 1.0 1.0 1.0
Dispatcher.getEndFlag() 1.0 1.0 1.0
Dispatcher.isSubrequest(PersonRequest) 4.0 6.0 7.0
Dispatcher.run() 3.0 14.0 14.0
Elevator.arrive() 1.0 1.0 1.0
Elevator.checkcurrentFloor() 1.0 3.0 4.0
Elevator.close() 1.0 1.0 1.0
Elevator.getCurrentFloor() 1.0 1.0 1.0
Elevator.getMainRequset() 1.0 1.0 1.0
Elevator.getStatus() 1.0 1.0 1.0
Elevator.getSubRequest() 1.0 1.0 1.0
Elevator.in(int) 1.0 1.0 1.0
Elevator.loopOut() 1.0 6.0 6.0
Elevator.method1() 1.0 9.0 9.0
Elevator.open() 1.0 2.0 2.0
Elevator.out(int) 1.0 1.0 1.0
Elevator.run() 4.0 10.0 12.0
Elevator.setMainRequset(PersonRequest) 1.0 1.0 1.0
Elevator.up(int,int) 1.0 17.0 23.0
Input.getFlag() 1.0 1.0 1.0
Input.Input(Quene) 1.0 1.0 1.0
Input.main(String[]) 1.0 1.0 1.0
Input.run() 3.0 7.0 8.0
Person.getInside() 1.0 1.0 1.0
Person.getRequest() 1.0 1.0 1.0
Person.Person(PersonRequest) 1.0 1.0 1.0
Person.setInside(int) 1.0 1.0 1.0
Person.setRequest(PersonRequest) 1.0 1.0 1.0
Quene.addNum() 1.0 1.0 1.0
Quene.getList() 1.0 1.0 1.0
Quene.getNum() 1.0 1.0 1.0
Quene.remove(int) 1.0 1.0 1.0
Quene.setList(Vector<PersonRequest) 1.0 1.0 1.0
Quene.setNum(int) 1.0 1.0 1.0
Quene.subNum() 1.0 1.0 1.0
Quene.write(PersonRequest) 1.0 2.0 2.0
Total 47.0 108.0 119.0
Average 1.27 2.92 3.22

Elevator.up()的複雜度比較高,緣由在於電梯的運行時須要判斷每一層是否有要進出的客人,而且須要更新其樓層運行狀態,所以複雜度較高。

SOLID分析

此次做業基本符合SOLID要求,知足SRP要求,每一個線程只作本身分內的事情,Input只負責輸入,Elevator只負責根據請求來上下運行,而Dispatcher負責從隊列裏取請求分配給電梯。每一個線程之間的耦合性很小。

1:單一性原則:添加了Dispacther類,使電梯線程再也不負責請求的調度。

2:開放封閉原則:這點沒有很好地實現,電梯在運行的地方不少都是硬編碼,不能很好的實現擴展。

3:里氏替換原則:因沒有使用Extends所以不存在此問題

問題分析

1:存在的問題主要是線程與線程之間的通訊太過頻繁,線程與線程之間的關聯還不夠分離。好比Input須要和Dispatcher交互,而Dispatcher須要和Elevator進行交互,頻繁的交互使程序邏輯顯得比較混亂。

2:優點,全部的地方取消了輪詢查詢,而是所有采起了wait()notify()機制,減小CPU沒必要要的執行時間,很好地迎合了多線程機制的規則。

第七次做業(多線程多電梯調度實驗)

此次是第三次多線程實驗,相比較前兩次實驗此次實驗使用了三部電梯,而且每部電梯有本身不一樣的停靠樓層,不一樣的搭乘上限,不一樣的運行速度。這就不能採起以前的硬編碼模式,須要對電梯的類進行改造。

電梯類:

private volatile int floor = 1; //初始化在1層
    private volatile Vector<Person taskList = new Vector<Person();
    private Dispatcher dispatcher;
    private String name;
    private long uptime;
    private int currentPersonNum = 0;
    private int space;
    private int[] stayFloor;
    private volatile int freeFlag = 1;

每一個電梯有本身的當前樓層,名字,本身的請求列表,本身的姓名,運行速度,電梯內空間與當前人數等私有屬性。還有一個重要的私有屬性,就是它的停靠樓層stayFloor,這是個一位數組,其在每一個電梯被建立的時候初始化。

int[] a = {-3, -2, -1, 1, 15, 16, 17, 18, 19, 20};
int[] b = {-2, -1, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
int[] c = {1, 3, 5, 7, 9, 11, 13, 15};
Elevator elevatorA = new Elevator("A", 400, 6, a);
Elevator elevatorB = new Elevator("B", 500, 8, b);
Elevator elevatorC = new Elevator("C", 600, 7, c);

這就初始化好了每個電梯的信息。

調度原則:

由於此次做業涉及換乘,所以調度策略發生改變,調度任務依舊由Dispatcher線程來執行。具體策略爲每來一個請求,調度器就根據現有電梯的停靠樓層判斷其是否須要換乘,若是須要換乘,則根據實際狀況將這個請求拆分紅兩個請求。例如FR0M-2-TO-3,則須要先從2-1,再從1-3,拆分紅兩個請求以後,咱們封裝一個類Person類。

private PersonRequest request1;
    private PersonRequest request2;
    private volatile int change = 0; //是否須要換乘
    private volatile int inside = 0;
    private volatile int first = 0;
    private volatile int second = 0;
    private volatile int finishFirst = 0; //是否已經完成第一次請求,等待執行換乘請求。

每一個Person對象記錄者本身有哪兩個請求,而且第一個請求是否已經執行完,調度器根據這個對象的信息,將其分派給相應的電梯,電梯執行完第一個請求,則將其還給調度器,讓調度器繼續將其分派給下一個電梯完成換乘任務。

public Person analyse(PersonRequest request) {
        Person person;
        int from = request.getFromFloor();
        int to = request.getToFloor();
        int id = request.getPersonId();
        int[] listFrom = getList(from); //得到停靠from樓層的電梯
        int[] listTo = getList(to); // 得到停靠to樓層的電梯
        int coincide = isCoincide(listFrom, listTo);
        /**
         * public Person(PersonRequest request1, PersonRequest request2,
         * int change, int first, int second)
         */
        if (coincide != -1) { // no need change
            person = new Person(request, null, 0, coincide, 0);
        } else { //須要換乘的狀況
            PersonRequest[] newRequest =
                    newTwoRequest(listFrom, listTo, request);
            person = new Person(newRequest[0], newRequest[1],
                    1, listFrom[0], listTo[0]);
        }
        return person;
    }

這個函數就是完成請求拆分的方法,先得到一個請求的from和to樓層,而後看哪些電梯可以到達這些樓層,若是有重合的,則不須要換乘,不然找最快的兩部電梯,到這兩部電梯重合處進行換乘。生成兩個新的請求。

電梯運行規則:

再也不設定捎帶請求,電梯根據本身的請求隊列以及運行方向來進行運行狀態的調整。若是當前運行狀態向上,就會把請求隊列裏同方向的請求所有一次性處理,直到沒有同方向的或者到達頂樓。若是一個請求執行完畢,而且其是個換乘請求則將其還給Dispatcher,若是其不需換乘,則直接將其從列表裏移除。

類圖:

時序圖

時序圖基本框架與第六次做業差很少

  • 1:DispactherInput的時序邏輯。

不一樣的地方在於Dispatcher線程的結束條件爲Input線程結束,而且本身的請求隊列爲空,而且全部的電梯的請求爲空。這是由於有時候電梯運行完一個換乘請求還會把剩下的請求還給調度器,若是提早結束,換乘請求的後半段就沒辦法完成了。

  • 2:DispatcherElevator的時序邏輯交互

此時的Dispatcher不能提早結束,須得等到本身的請求隊列爲空,而且三部電梯請求隊列也爲空而且Input線程向其發送End標識時,才能結束。

代碼規模

Source File Total Lines Source CodeLines
Clock.java 3 3
Dispatcher.java 291 246
Elevator.java 320 276
Input.java 91 70
Person.java 61 45
Quene.java 22 16
Total: 788 656

代碼複雜度

method ev(G) iv(G) v(G)
Dispatcher.analyse(PersonRequest) 1.0 2.0 2.0
Dispatcher.check() 1.0 6.0 6.0
Dispatcher.Dispatcher(Elevator[],Quene) 1.0 1.0 1.0
Dispatcher.down(int,int,PersonRequest) 6.0 10.0 12.0
Dispatcher.getEndFlag() 1.0 1.0 1.0
Dispatcher.getList(int) 4.0 3.0 5.0
Dispatcher.getQuene() 1.0 1.0 1.0
Dispatcher.isCoincide(int[],int[]) 4.0 1.0 4.0
Dispatcher.newTwoRequest(int[],int[],PersonRequest) 7.0 11.0 13.0
Dispatcher.run() 3.0 23.0 23.0
Elevator.arrive() 1.0 1.0 1.0
Elevator.close() 1.0 1.0 1.0
Elevator.compareFloor(int) 2.0 1.0 2.0
Elevator.Elevator(String,int,int,int[]) 1.0 1.0 1.0
Elevator.findMaxTask(int) 1.0 6.0 6.0
Elevator.getCurrentFloor() 1.0 1.0 1.0
Elevator.getFreeFlag() 1.0 1.0 1.0
Elevator.getIndex(int) 3.0 1.0 3.0
Elevator.getStayFloor() 1.0 1.0 1.0
Elevator.getTaskList() 1.0 1.0 1.0
Elevator.getUptime() 1.0 1.0 1.0
Elevator.in(int) 1.0 1.0 1.0
Elevator.method1() 2.0 6.0 9.0
Elevator.open() 1.0 2.0 2.0
Elevator.out(int) 1.0 1.0 1.0
Elevator.outThisfloor() 3.0 23.0 24.0
Elevator.run() 5.0 8.0 9.0
Elevator.setDispatcher(Dispatcher) 1.0 1.0 1.0
Elevator.setFreeFlag(int) 1.0 1.0 1.0
Elevator.up(int,int) 2.0 6.0 10.0
Input.getFlag() 1.0 1.0 1.0
Input.Input(Quene) 1.0 1.0 1.0
Input.main(String[]) 1.0 1.0 1.0
Input.run() 3.0 5.0 6.0
Person.getChange() 1.0 1.0 1.0
Person.getFinishFirst() 1.0 1.0 1.0
Person.getFirst() 1.0 1.0 1.0
Person.getInside() 1.0 1.0 1.0
Person.getRequest1() 1.0 1.0 1.0
Person.getRequest2() 1.0 1.0 1.0
Person.getSecond() 1.0 1.0 1.0
Person.Person(PersonRequest,PersonRequest,int,int,int) 1.0 1.0 1.0
Person.setFinishFirst(int) 1.0 1.0 1.0
Person.setInside(int) 1.0 1.0 1.0
Quene.getList() 1.0 1.0 1.0
Quene.setList(Vector<PersonRequest) 1.0 1.0 1.0
Quene.write(PersonRequest) 1.0 2.0 2.0
Total 79.0 146.0 168.0
Average 1.68 3.11 3.57

SOLID分析

1:單一性原則:每一個類的職責明確,Input只負責輸入,Elevator只負責根據請求來上下運行,而Dispatcher負責從隊列裏取請求分配給電梯。每一個線程之間的耦合性很小。

2:開放封閉原則:實現軟編碼,能夠添加新的電梯進入到這個系統,而且能夠在原基礎上更改Dispatcher以修改調度策略。

3:里氏替換原則:因沒有使用Extends所以不存在此問題

存在問題

  • 調度策略比較簡單:優勢是調度策略簡單明瞭,可是因爲調度的過程是靜態的,即來了請求就將其拆分,而沒有考慮實際的電梯位置以及電梯裏的空間等因素,卻少動態的考慮,致使一些請求的調度不理想,或者一些電梯空閒等待等不良狀況的發生。

三:做業存在的BUG

第五次做業:

  • 存在問題

結束進程的判斷失誤。當Input進程都到null請求時,發出Input結束信號,Elevator讀到這個請求就結束了本身的線程。但其實隊列中還存在請求沒有執行完。

  • 解決方案:

在電梯收到Input線程結束的請求時,再判斷一下請求隊列是否還存在未完成的請求,若是沒有,則電梯線程也結束,不然繼續執行直到請求隊列爲空。

while (true) {
            if (Input.getFlag() == 1 && Quene.getNum() == 0) {
                break;
            }

第六次做業:

  • 存在問題

仍然是Dispatcher線程提早終止的問題。當Input請求發出終止信號時,Dispatcher去判斷Quene是否爲空,若是爲空,則Dispatcher也終止。但實際上調度器本身的隊列裏還有請求沒有分配給電梯執行。

  • 解決方案

在受到Input的結束請求時,Dispatcher增長一條對本身請求隊列是否爲空的判斷,當input請求結束,Quene請求隊列爲空,本身的請求隊列爲空時,向電梯線程發送Dispatcher結束的信號。

while (true) {
            if (quene.size() == 0 && Input.getFlag() == 1
                    && requsetQuene.getList().size() == 0) {
                endFlag = 1;
                break;
            }

第七次做業

  • 存在問題

電梯運行狀態的問題,電梯每次都會根據第一條請求肯定運行方向,但若是當第一條請求爲頂層,而且電梯已經滿,則電梯在20層時又會進行一次判斷,但它仍是會向上,所以這是就會出現一個死循環。

  • 解決方案

當電梯運行到頂層或最低層的時候進行判斷,改變其運行方向。

if(this.floor==stayFloor[stayFloor.length-1]){
            k = -1;
        }
        if(this.floor == stayFloor[0]){
            k = 1;
        }

這裏的判斷,當到了當前電梯的最底層時,方向向上,最高層時運行方向向下,就不會發生死循環的狀況了。

4、驗證方法:

爲了模仿測評機的按時輸入,我利用python以及linux的shell寫了測試腳本,提取每條指令的時間,而後按時將指令放到管道中去,而後重定向到標準輸入,模擬了在肯定時間輸入的狀況。

解析時間:

import time
f = open(r"/Users/chenzeyin/Desktop/data",'r')
temp=f.readline()
sleeptime = []
req = []
lastTime = 0
while temp:
    temp = temp.strip("\n")
    gg = temp.split("]")
    gg[0]=gg[0][1:]
    sleeptime.append(float(gg[0])-lastTime)
    lastTime = float(gg[0])
    req.append(gg[1])
    temp = f.readline()    
sleeptime =[2.3,1.3]
req=["1-FROM-2-TO-5","2-FROM-3-TO-4"]
for i in range(len(req)):
    #print(sleeptime[i])
    time.sleep(sleeptime[i])
    print(req[i])
f.close()

測試

import os
import time
path = "/Users/chenzeyin/Desktop/czy/git/Elevator3/out/production/Elevator3"
def ownTest():
    os.chdir(path)
    os.system(r"python -u /Users/chenzeyin/Downloads/timeInput.py|java -Djava.ext.dirs=/Users/chenzeyin/Downloads Input/Users/chenzeyin/Desktop/out")
ownTest()

5、學習心得與總結

  • 一、多線程是一個很是困難可是有意思的章節。咱們在學習多線程的時候要抓住一個大的方向,共享對象資源,當多個線程去搶奪一個資源時須要對那個對象的資源加鎖,要搞懂synchronized的用法,而且不要盲目使用該方法,由於這會增長CPU沒必要要的開銷。

  • 二、同時,儘可能使用線程安全的容器,如vector,這樣會減小線程衝突的可能性。
  • 三、合理使用wait(),notify()機制,減小CPU不必的浪費時間。

相關文章
相關標籤/搜索