本次做業模擬一個多線程實時單部電梯系統,從標準輸入中輸入請求信息,程序進行接收和處理,模擬電梯運行,將必要的運行信息經過輸出接口進行輸出。程序員
電梯樓層:1層到15層,共15層;電梯初始位置:1層;輸出信息包括兩部分,電梯的狀態和人的狀態算法
電梯的狀態,分爲兩種,OPEN(開門),CLOSE(關門)安全
開門格式:OPEN-樓層(在開門剛開始輸出);關門格式:CLOSE-樓層(在關門剛結束輸出)數據結構
人的狀態,分爲兩種,IN(進入電梯),OUT(離開電梯);進入電梯格式:IN-id-樓層,這裏的id是這我的本身的id;離開電梯格式:OUT-id-樓層多線程
按照請求進入系統的時間順序,依次按順序逐個執行運送任務(單人電梯);併發
主線程進行輸入的管理,使用ElevatorInput,負責接收請求並存入隊列;優化
開一個線程(電梯線程),用於模擬電梯的活動,從隊列中取出請求並進行執行,執行完後繼續取繼續執行;this
構建一個請求隊列,用於管理(從輸入得到,並能夠交給電梯線程)請求,且須要保證隊列是線程安全的。spa
從此次做業開始引入了多線程問題,大大提高了做業難度,之前所接觸的程序都是單線程的,按照順序執行,而如今是多個線程同時併發,對於JAVA代碼在JVM虛擬機下如何運行的程序員不能看到,只能經過代碼和輸出結果進行分析,這對於debug來講是一件很頭疼的事。線程
多線程與進程併發是同一個意思,也就是多個線程互相爭搶CPU資源,同時會共享部分數據,所以須要考慮同步與互斥問題。
其實咱們以前寫的程序中Main方法就是一個調度整個代碼文件的線程,整個工程運行時就是從Main方法開始進行單線程運行。此次做業的Main線程主要負責建立輸入對象和電梯對象,進行交互,而後輸入類、電梯類處理本身的事情,下降代碼耦合度。
下面用Thread線程類建立線程並調用線程運行。
1 Thread input = new Thread(new Input()); 2 Thread elevator = new Thread((new Elevator())); 3 input.start(); 4 elevator.start();
系統評測提供輸入格式的處理,咱們只需根據輸入構造接收容器便可,也就是一個請求隊列,數據類型是PersonRequest,從而獲取request的屬性和數據。
因爲考慮輸入數據的時間和數量是沒法肯定的,因此咱們把線程進入死循環,當人工運行輸入手動結束或者評測機加結束符時爲結束條件,退出循環,關閉輸入,中止線程運行。
Queue<PersonRequest> requestQ = new LinkedList<PersonRequest>();
首先,電梯須要構造本身的屬性和方法,格式上與request保持一致。而後因爲電梯須要與其餘線程併發,因此電梯繼承Thread類。
第一次電梯運行處理比較簡單,逐個獲取請求隊列中的請求,而後逐個執行便可,在運行時間上不用考慮優化。當請求隊列爲空且輸入中止時中止線程,程序結束。
電梯這個類中不只須要得到請求,還要將請求進行「運送」,也就是執行請求(電梯本質的功能):運行至請求輸入樓層開門,電梯進人,關門運行,到達開門,再關門……
1 private int personId; 2 private int fromFloor; 3 private int toFloor; 4 private int nowFloor; 5 6 private int getFloor; 7 8 private static final int optime = 200; 9 private static final int cltime = 200; 10 private static final int runtime = 400; 11 12 public RunElevator(int personId, int fromFloor, 13 int toFloor, int nowFloor) { 14 this.personId = personId; 15 this.fromFloor = fromFloor; 16 this.toFloor = toFloor; 17 this.nowFloor = nowFloor; 18 }
本次做業比較簡單,在邏輯上幾乎是沒有問題的,也不須要優化,因此基本是邏輯清楚就一遍就過。互測debug的時候也沒有找到bug。只要保證電梯運行合法便可,在該停的樓層停,該進人的時候進人,該關門的時候關門。須要注意的是,第一個請求輸入起始樓層不必定是1層,因此須要先運行至1層再開門進人,如果1層則直接開門;還有不能同時開關門屢次,也不能往返運行同一個請求。
代碼和類都比較少,說明第一次做業真的很順暢,心情很好。
第二次附加了捎帶的條件,考慮能夠多人同時乘坐的電梯調度,同時限制了運行時間,必須使用多線程併發模式。
1.主請求選擇規則:
若是電梯中沒有乘客,將請求隊列中到達時間最先的請求做爲主請求
若是電梯中有乘客,將其中到達時間最先的乘客請求做爲主請求
2.被捎帶請求選擇規則:
電梯的主請求存在,即主請求到該請求進入電梯時還沒有完成
該請求到達請求隊列的時間小於等於電梯到達該請求出發樓層關門的截止時間
電梯的運行方向和該請求的目標方向一致。即電梯主請求的目標樓層和被捎帶請求的目標樓層,二者在當前樓層的同一側。
3.注意:
電梯不會連續開關門。即開門關門一次以後,若是請求隊列中還有請求,不能當即再執行開關門操做,會先執行請求。
在保證輸入條件與第一次做業相同(即不斷地進行讀取不一樣時間和相同時間的輸入請求),因爲電梯運行時間有限制,須要考慮捎帶狀況,以減小運行時間(便可以多人乘坐的狀況下,將順路捎帶)。
這會帶來一些複雜度的考慮,也會帶來請求隊列和調度的問題,同時也會有數據結構上處理的麻煩,也會有線程安全問題。
Main線程保持不變,而後輸入線程和電梯線程併發運行,注意考慮線程安全問題,以及線程之間的關係,輸入線程是將請勿放入隊列,而電梯線程是從請求隊列得到請求。
輸入線程和第一次沒有多少改變,我在輸入結束後增長一個標記,該標記是電梯中止ElevatorStop類的對象.
eleInput.close();
stop.runStop(); //stop flag
調度器在做業中起到管理請求隊列,與輸入線程和電梯線程之間進行數據交互,輸入線程輸入的請求在調度器的隊列中保存,電梯線程又從中取出執行。
調度器自己不是線程,但被輸入和電梯兩個線程併發使用這個對象。
我此次做業用了一個輸入保存請求隊列和一個電梯裏運行的請求隊列。
private ArrayList<PersonRequest> requestV = new ArrayList<>(); // input
private Vector<PersonRequest> runRequest = new Vector<>(); // run
這裏是線程考慮加鎖,保住安全性。
電梯獲取請求的方法getRequest,當隊列爲空時,若是輸入結束直接返回空標誌線程結束,輸入未結束則電梯線程等待。
1 public synchronized void add(PersonRequest request) { 2 requestV.add(request); 3 notifyAll(); 4 } 5 public synchronized boolean isEmpty() { 6 return requestV.isEmpty(); 7 } 8 public synchronized PersonRequest getRequest() { 9 try { 10 while (isEmpty()) { 11 if (stop.getStop() && isEmpty()) { 12 return null; 13 } else { 14 wait(); 15 } 16 } 17 return requestV.get(0); 18 } catch (Exception e) { 19 e.printStackTrace(); 20 return null; 21 } 22 }
電梯線程在第一次基礎上增長捎帶的功能,同時從調度器中得到請求,若是有知足捎帶的請求,繼續添加到運行隊列中,一併處理。
捎帶是本次做業的難點,較爲複雜的算法,Bug也是這個地方出現的,代碼也是這裏開始增加的。
1 public synchronized int proRequest(int nowfloor) { //return equal nowfloor 2 int i; 3 for (i = 0; i < requestV.size(); i++) { 4 if (requestV.get(i).getFromFloor() == nowfloor) { 5 return i; 6 } 7 } 8 return -1; 9 } 10 11
我在這裏優化爲靠近當前運行方向樓層的最早出電梯,這是較好的算法。
1 public int getDown() { // return highest floor of down elevator 2 int i; 3 int k = -3; 4 int index = -1; 5 for (i = 0; i < runRequest.size(); i++) { 6 if (runRequest.get(i).getToFloor() >= k) { 7 k = runRequest.get(i).getToFloor(); 8 index = i; 9 } 10 } 11 return index; 12 } 13 14 public int getPro() { // return lowest floor of up elevator 15 int i; 16 int k = 16; 17 int index = -1; 18 for (i = 0; i < runRequest.size(); i++) { 19 if (runRequest.get(i).getToFloor() <= k) { 20 k = runRequest.get(i).getToFloor(); 21 index = i; 22 } 23 } 24 return index; 25 }
此次不是特別開心,由於一個bug讓我沒有經過,痛哭啊!!!本次做業的調度算法增長了難度,須要考慮時間優化,因此邏輯上、優化上出現了不少問題。
捎帶狀況和優化如上圖,有一點就是在輸入線程時,第一次輸入可能有多個輸入同時間輸入,就必須在第一次同時被執行,這一點難題折磨了我許久,直到最後的截止時間。首先保證線程安全,而後因爲我是讀一個請求就喚醒電梯線程,此時尚未第二個請求讀入,但有可能它們是同一時間但分步讀入的請求,電梯就會判斷不到有知足捎帶的請求,所以默認不捎帶了。增長了負樓層,且沒有零層,這和實際生活相符,不過這一點只是增長了條件判斷,不會特別難。須要注意一下方向和運行停靠問題便可。
本次代碼仍是比較長的,增長了調度器類,電梯線程中止類,實質上的多線程就這樣開始了。心情瞬間涼了,難受。邏輯性更強了,還增長了多種狀況,須要各類方法來調度。
類中的方法變多的緣由是數據結構的處理,還有調度器與兩個線程之間的數據交互,還有捎帶狀況調度問題。
本次做業,須要完成的任務爲多部多線程智能(SS)調度電梯的模擬,在第二次做業基礎上增長至3部電梯同時運行,且每部電梯的工做狀態、工做條件和工做需求都不相同。
電梯可運行樓層:-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名
任何電梯均可以在任意合法的樓層ARRIVE,可是隻能在本身所被容許停靠的樓層進行停靠;
電梯任什麼時候候,內部的人數都必須小於等於轎廂容量限制。也就是說,即使在OPEN、CLOSE中間,也不容許出現超過容量限制的中間狀況;注意請求拆分後的執行順序,不能跨越也不能超前執行。
本次做業中,乘客須要在合適的樓層中途更換電梯,因此須要將請求按照必定的規則進行拆分處理,分給其他電梯一塊兒工做。不過若是這樣作的話,必需要考慮多部電梯前後順序的控制。
構建一個調度器(本次的調度器能夠和隊列是一體的)
用於管理請求
和電梯進行交互並指派任務給電梯,並可能須要處理請求的前後順序依賴關係
且須要保證調度器是線程安全的
構建三個電梯線程,每一個電梯的行爲功能是同樣的,只是三個不一樣對象,各自屬性成員有相應的區別:運行時間,載荷量。
Main線程依舊保持不變,增長了三個電梯線程。
第二次做業的調度器已經再也不知足全部的需求了,但不是沒有用,依然保留原有的調度器,用來管理請求隊列,同時我增長了一個請求拆分的類,用來對請求進行判斷拆分,還增長了數據結構,即對應電梯各自的運行隊列。
1 Thread eleA = new Thread(elevatorA); 2 Thread eleB = new Thread(elevatorB); 3 Thread eleC = new Thread(elevatorC); 4 eleA.start(); 5 eleB.start(); 6 eleC.start(); 7 8 private long runtime; // 電梯運行時間不一樣 9 private int volume; // 電梯載客不一樣 10 private String elename; // 電梯標號 11 private int nowvolume = 0; // 當前載客量 12 13
經過對請求的起始樓層和到達樓層進行判斷,看是否可以直接用1個電梯執行,或者更優的電梯運行,同時要考慮到電梯容量,若是A電梯容量滿了,它知足B電梯,那麼能夠交給B電梯,這是優化思想。也能夠繼續等待A電梯執行完成,再繼續執行,畢竟A電梯是最快的電梯。分割厚須要保證請求的執行順序,若分割爲A電梯-3->1,C電梯1->3層,則必須等A電梯將請求執行完成,才能讓C電梯執行。
此次做業我沒有所有完成,因此確定還存在許多問題,沒有進入互測階段。以前遇到的狀況就是多個電梯線程沒法停下來,這個問題困擾了我半天的時間,最後請求同窗才解決的,這些問題以前沒有遇到過,博客裏也沒有詳細的說明,課上更沒有提到,因此踩到這個坑真的沒辦法。同時wait,和notifyall的使用須要注意,我一直覺得這個是喚醒當前調用它的對象以外的線程,但不是這樣,這個是針對當前鎖住的對象,只要訪問該對象的數據,就屬於等待或者被喚醒,這是默認狀況。咱們在使用的過程當中能夠在前面用對象調用這兩個方法,就能夠指定電梯線程的喚醒了:eleA.notify().
其餘bug就是邏輯問題,此次增長了請求拆分,這個狀況比較複雜,判斷條件多,因此是bug增多的地方。
代碼長度在這個單元裏增長了許多,一個是隊列數據結構的操做,好在數據結構操做能夠複用代碼,且處理簡單。可是在多線程處理的代碼上會比較糾結,以及隊請求的分割操做是最麻煩,且邏輯最容易出錯的地方。調度問題使得代碼行數大大增長。
複雜度就不是一張圖能說明的了,而是三張圖,可見,這道題的處理是真的複雜,在代碼構造的途中不斷進行封裝,類也比較多,方法也是不少。