第一次做業相對比較基礎,個人設計上輸入端向請求隊列類添加請求、電梯線程從請求隊列中依次取出請求,兩個動做互斥,所以我對請求隊列類方法上鎖,解決寫寫衝突問題,保障線程安全。安全
關於程序結束的實現,因爲此次電梯處理請求遵循FIFO原則,且一個請求僅會被添加到請求隊列一次,所以文件結束標誌null能夠做爲線程結束的flag。多線程
第二次做業再也不是無腦套用生產消費模型,電梯捎帶需求對於電梯使用者隊列更新與處理提出要求。我在請求隊列的getRequest方法中實現電梯捎帶,即將電梯當前樓層,電梯運行方向信息做爲參數傳入該方法,每一次電梯到達一個樓層(完成電梯任務或僅作Arrive動做),getRequest方法都會遍歷請求隊列,將可捎帶請求加入電梯使用者隊列。架構
關於電梯任務完成的優化,電梯的「出人」方法中,每一次要遍歷使用者隊列,將當前樓層可出的使用者所有送出。我將電梯隊列中未離開的首個請求做爲主請求,在從當前樓層前去「取人」以及將該人送到目標樓層的過程當中,每一層都判斷是否要出人是否要進人;在到達主請求樓層時,電梯不當即換向,先遍歷使用者隊列,將使用者中目標樓層同向的使用者都送走後,再調頭。框架
此次做業的程序退出方式難度較大。基於個人設計,電梯從請求隊列中取人不遵循FIFO策略,null不知足任何「捎帶條件」所以將null傳入使用者隊列須要特判,另外一方面,若是null傳入了使用者隊列,電梯完成任務時,每一次遍歷使用者的目標樓層時,都要添加使用者不是null這一個條件,使代碼十分不美觀。所以,在此次設計中我在請求隊列類中添加End標記,若是請求隊列僅有null一個元素(null不傳入電梯,其他需求都會以非捎帶或捎帶方式進入電梯),end標記爲1,當電梯使用者隊列爲空而且電梯調度器爲End狀態時,電梯結束運行。ide
第三次做業中電梯增長了限乘人數,限到樓層,三部電梯間換乘。在這個需求下,三部電梯在消費者角色以外,若有使用該電梯的乘客須要換乘,那麼這部電梯又承擔了生產者角色,將乘客放回總請求隊列。工具
三部電梯的捎帶問題仍在調度類getRequest方法中實現,本次做業中向getRequest方法傳入參數更復雜,從總請求隊列中向電梯使用者加人受電梯承載力的限制。遍歷總請求隊列時,優先選擇這部電梯能直接送到最終目的地的請求,若是須要,應從換乘隊列中刪掉這我的;若是電梯尚可載人,再將須要換乘的請求加入電梯使用者隊列,並將換乘請求加入換乘者隊列。性能
電梯完成任務方式相似於做業2,須要注意的細節是保證每一層電梯要先下後上,避免超載。學習
線程結束的方式中,調度類End新增條件,換乘隊列爲空,避免人員還沒有送達,電梯結束運行。測試
75行代碼優化
屬性:
private OpList elOp; //共享請求隊列 private int floor = 1; //當前樓層 private static final int door = 250;//開關門時間 private static final int go = 500;//移動時間
方法:
public Elevator(OpList op) {} public void Op(PersonRequest pr) {}//電梯完成請求,20行 private void printOpen(int floor) {}//封裝開門輸出 private void printClose(int floor) {}//封裝關門輸出 private void printIn(int floor,int id) {}//封裝進人輸出 private void printOut(int floor,int id) {}//封裝出人輸出 public void run() {}
40行代碼
屬性:
private ArrayList<PersonRequest> wait;//請求隊列 private static int limit = 1;
方法:
public OpList() public synchronized PersonRequest getRequest() //取請求,10行 public synchronized void setRequest(PersonRequest newReq)//增長請求,10行
30行代碼
屬性:
private OpList rawInput;//共享請求隊列 private ElevatorInput elevatorInput = new ElevatorInput(System.in);
方法:
public Producer(OpList raw) @Override public void run() //往請求隊列中加入請求
第一次做業是生產消費模型的基礎實現,所以代碼複雜度低,耦合度低
十分基礎的架構,Elevator類是消費者,Producer類是生產者,共享對象OPList類。Main類中調用兩個線程。
230行
屬性:
private ArrayList<Person> users;//使用者隊列 private int floor = 1;//電梯所在樓層 private Request eleOp;//電梯當前主請求 private RequestList rl;//共享對象,請求隊列 private static final int move = 400;//電梯移動時間 private static final int door = 200;//電梯開關門時間
方法:
public Elevator(RequestList re) {} private void printOpen(int floor){} private void printClose(int floor) {} private void printArrive(int floor) {} private int out(int floor,int mark1) {}//在特定樓層狀況,25行 private int operateDown(int from,int to,int count) {}//電梯下行過程處理,30行 private int operateUp(int from,int to,int count) {}//電梯上行過程處理,30行 private void operate(Person per,int from,int to,int choice,int count) {}//電梯完成主請求,57行 public void run() {}//60行
40行
屬性
private RequestList rawInput;// 共享對象 private ElevatorInput elevatorInput = new ElevatorInput(System.in);
方法:
public Input(RequestList req) {} @Override public void run() {}
RequestList類:
114行
屬性
private ArrayList<Person> wait;// 請求隊列 private int end = 0;//結束標記爲 private int in = 0;//區別第一次取人標記
方法
public RequestList() {} private void printOpen(int floor) {} public synchronized int getRequest(ArrayList<Person> ele,int floor,int des,int marker) {}//根據電梯狀態從請求隊列中取出請求,60行 private void printIn(Person per,int floor) {} public synchronized void setRequest(Person newReq) {}//從輸入端向請求隊列中增長請求,5行 public boolean empty() {}//判斷當前請求隊列是否爲空或僅含有null public int getEnd() {}//判斷輸入線程是否結束輸入(傳入null)
Request類:封裝請求
45行
屬性
private int from;//請求中出發樓層 private int to;//請求中到達樓層 private String status = new String("");//請求中方向
方法
public Request(int from,int to) {}//獲得請求方向信息 public boolean carry(Person per) {}//判斷是否人的請求同向可攜帶 public int getFrom() {} public int getTo() {} public String getStatus() {} public void reset(int newFrom,int newTo) {}//請求重置
25行
對PersonRequest進行封裝
在PersonRequest基礎上增長屬性,方便捎帶與調度
private boolean state;//是否進入電梯 private boolean out;//是否出電梯
Elevator類中三個operate方法耦合度高,緣由是三個方法實際是電梯完成任務上行、下行、送到目標樓層全過程的分割,operateUp與operateDown都是operate中的一部分,而run方法中operate又是重要的部分。operateUp與operateDown兩個方法設計不夠巧妙,內容大多爲對稱映射關係。run方法複雜度高,耦合度高,主要由於run方法中爲了實現優化,在實現取request,完成request的基礎操做外,用循環遍歷電梯使用者隊列,調用operate方法,儘量多的將同向運行的乘客送出,這就致使個人run方法寫的龐大不美觀。
RequestList類的getRequest方法也是複雜度高,耦合度高的方法。耦合度高是由於電梯的調度在個人設計中是以這個方法爲基礎,經過電梯類中每一層都調用這個getRequest方法,經過返回參數,判斷該層是否能夠捎帶進人,所以在elevator類中重複調用這個方法。其複雜度高的緣由在於,在這個方法中,嵌套了4層if else判斷,並在最外層if else的每一種狀況下都對請求隊列進行遍歷。
Person類封裝了PersonRequest,Request類對人的請求與電梯運行任務作了統一。Elevator類是消費者,與Input類共享RequestList對象。RequestList類中的請求隊列以及Elevator類中的使用者隊列都是以Person爲單位的ArrayList,Elevator類與PersonRequest類中,對於電梯當前運行狀況以及等待着請求用Request類封裝,判斷是否能夠捎帶。
384行
屬性
private static final int door = 200;//關門時間 private final int capacity;//承載力 private final int move;//移動時間 private final String name;//電梯名稱 private final ArrayList<Integer> floors = new ArrayList<Integer>();//可達樓層 private ArrayList<Person> users = new ArrayList<Person>();//使用者 private final ArrayList<Integer> setFloors = new ArrayList<Integer>();//與其餘電梯交叉樓層 private int floor = 1;//當前所在樓層 private Request eleRep;//電梯當前主要請求 private Channel ch;//共享對象 private int first;//標誌接過第一我的
方法:
goUP,goDOWN,operate,run僅在第二次做業基礎上稍加改動
public Elevator(String str,Channel channel) {} public int scanFloor(int floor) {}//判斷某樓層是否在可達樓層中 public void addFirst() {} public boolean isFull() {} public int getNearest(int oto,int afrom) {}//換乘目標樓層,20行 public int getFirst() {} public String Name() {} private void printOpen(int flo) {} private void printClose(int flo) {} private void printArrive(int flo) { } private int out(int mark1) {} private boolean findFloor(int floo) {} public int goUp(int to) {} public int goDown(int to) {} private void operate(Person per,int des,int choice) {} private boolean isIn(Person person) {} public void run() {}
170行
屬性:
private ArrayList<Person> wait; //請求隊列 private ArrayList<Elevator> eles; //電梯線程池 private ArrayList<Integer> ag; //須要換乘者標記隊列 private boolean end = false; private boolean end1 = false; private int carry = 0;
方法:
public Channel() {} private int fin(int id) {} //遍歷換乘者隊列 public synchronized void addRequest(Person per) {}//從輸入端增長請求 public boolean isEnd() {} //判斷請求隊列爲空且輸入結束且換乘隊列爲空 private void printOpen(int floor, String str) {} private void printIn(Person per, int floor, String str) {} public synchronized int getRequest(Elevator ele, ArrayList<Person> users,int floor, int des, int marker, int ca) {} //向電梯使用者隊列投放請求 ,90行 public boolean getEnd() {} public void startEle() {}
屬性:
private final int id; private boolean stateIn; //電梯內判斷位 private boolean stateOut; //出電梯判斷位 private final int ofrom; //原始請求出發樓層 private final int oto; //原始請求到達樓層 private int afrom; //實際出發樓層 private int ato; //實際到達樓層 private ArrayList<String> inList; //搭乘過電梯標記隊列
方法:
public Person(PersonRequest pr) {} public boolean getInS() {} public boolean getOutS() {} public void setStateIn(boolean bo) {} public void setStateOut(boolean bo) {} public boolean canIn(Elevator ele) {} //判斷是否可僅當前電梯 public void setTo(int to) {} public void setaFrom(int from) {} public int getaFrom() {} public void setOut(Elevator ele) {} //根據當前所進電梯判斷實際到達樓層爲多少 public boolean oCanOut(Elevator ele) {} //判斷當前電梯是否能夠直接送到原始目標樓層 public int gi() {} public int getaTo() {} public int getoTo() {} public void addIn(String str) {}
同第二次做業
同第二次做業
getRequest方法複雜度高,嵌套4層if else判斷,且每個條件分支下都有遍歷。對於請求隊列與換乘者隊列,存在遍歷後刪除某些項,重建隊列的操做。耦合度高,由於該方法在Elevator類的operate方法中被調用頻率太高。
Elevator類的goUP,goDOWN,operate方法耦合度高的緣由同第二次做業。run方法的複雜度,耦合度圈複雜度高,緣由也同第二次做業。
Person類CanIn複雜度高由於其內部有4層if嵌套,且最後一次嵌套中存在對使用過電梯隊列的遍歷。
Channel線程中的線程池中建立三個Elevator類,Elevator類與Input類共享Channel中的waitList對象,三個Elevator類共享一個Channel總調度器。
Person類與Request類是不可再拆分的原子類。
第一次做業中,公測與互測未發現bug,可是在本身寫代碼的時候,因爲不熟悉鎖機制,對OPList方法加物理鎖後,未及時notifyAll,出現了死鎖。
第二次做業中,剛開始沒有處理好Elevator線程終止判斷方案,致使程序終止不了,最終RTLE報錯。最後發現bug點在於個人Elevator判斷終止條件爲usersList僅有null一個元素,可是PersonRequestList在一些狀況下未將null傳入Elevator類。這個問題不算是線程安全問題,主要是設計不夠完善。最終,我轉變思路,對共享對象類設計End標記位,向Elevator類傳遞結束信號,保證程序正常結束。
第三次做業中,我僅對addRequest與getRequest兩個共享對象中的方法上物理鎖,其他對象,均爲單一線程享有。這個設計保障了線程安全性。可是單一Elevator線程內部,我對於電梯使用者隊列的「取與刪除」的安全性設計存在缺陷。致使我在本地測試中出現了「乘客電梯中失蹤「,乘客尚未輸入「IN」,就被從電梯使用者隊列中刪去的問題。前者我在本地測試中勉強補救,後者在本地測試中未發現,致使強測中丟失了兩個點。
總的來講,個人設計儘可能避免了「共享」,上鎖侷限在對方法上鎖,必定程度上保證了線程安全性。然而,拋開線程安全性,個人設計中,在電梯類調度優化時,考慮並不全面,對於電梯的使用者隊列的維護不夠完善,致使最終電梯使用者隊列的增長與刪除出現了衝突,出現了bug。
在第一次做業互測階段,我閱讀別人的代碼,發現你們實現的思路框架基本一致,應該不存在設計上的bug。
第二次做業互測階段,因爲未搭建評測機,我只有嘗試輸入本身本地測試的數據,對同屋的人代碼隨機測試,無奈沒有發現bug。
第三次做業互測階段,已經搭建好評測機,我經過data.py生成隨機數據,通過TestClass文件解析時間戳,將輸入流定時輸入到同屋小夥伴的代碼中,並將輸出結構遞交給check.py。check.py對獲得的輸出數據按照指導書中的正確性要求進行檢查,若是知足所有正確性要求,則循環回到data .py生成隨機數據這一步;不然,循環中止,cmd界面顯示報錯信息。循環生成隨機數據測試法親測有效。
與第一單元互測階段發現bug相比,第二單元發現別人程序bug難度更大。想要有效發現別人程序的bug,必定要創建起一套完整的,成體系的找bug工程。
首先,第二單元的定時輸入是咱們沒法經過idea輸入端手動完成的,咱們必須經過.sh文件以及時間戳解析文件,將帶時間戳的輸入信息保存到文本文檔,輸入其餘人的程序中進行測試。
其次,第一單元的前兩次做業,把目標放在檢查別人對於輸入是否合法的判斷是否全面,死抓WRONG FORMAT錯誤就能夠狼到一些人,可是第二單元,輸入流是官配的,狼人重點放在了對於合法的輸入,其餘人的代碼輸出的正確性問題。
第三,第二單元中,輸出正確性判斷難度較大。第一單元中,經過MATLAB,計算求導結果尚能夠判斷輸出結果是否正確,到了第二單元,尤爲是是第三次做業,三部信息交叉輸出,輸出正確性斷定條件較多,尤爲是在輸出數據較多時,肉眼幾乎沒法判斷輸出是否正確。所以,第二單元判斷別的輸出正確性必定須要正確性解析代碼。
第四,第二單元中,因爲多線程輸出的不肯定性,一些本地輸出錯誤結果的測試樣例,提交到評測平臺時,獲得了正確的輸出結果。這種狀況在第一單元輸出結果肯定的狀況下是萬不會發生的。多線程不安全設計形成的bug的不可復現性要求咱們提升測試數據的投入量。
一、線程安全
這三次做業中,爲了保障線程安全性,我儘可能作到減小共享,防止衝突的發生。對於共享對象的上鎖,我侷限於共享對象類的方法上鎖,保障一個線程調用該方法時,一個共享對象實例所有被鎖住。個人實現方法雖然說提升了線程安全性,可是靈活性不夠,這暴露了我對於Java的線程安全以及鎖機制的理解不夠透徹。
通過研討課同窗的分享以及互測中閱讀其餘同窗代碼,我認爲在後續多線程學習中我應該主動嘗試對共享類的某個屬性上鎖,學習並使用可重入鎖,使用atomic包的原子性工具。用更豐富,更靈活的手段保障線程安全。
二、設計原則
相比於第一單元每一次做業都重構的情況,本單元我格外注意代碼的可擴展性,第一次做業搭建好生產者消費者架構,爲後兩次做業提供基礎,第二次做業的電梯調度策略可直接遷移到第三次做業中單個電梯線程的調度設計中。能夠說,三次做業的設計思路是一脈相承的。
本單元我在單一責任原則上作得很不完善。第二次做業開始,電梯調度策略的實現所有裝入run方法,使run方法冗長難看不說,還致使了電梯調度過程當中,使用者隊列的安全性得不到維護,最終潛藏的隱患遺留到了第三次做業中集中爆發,強測掛點。這個問題的根源是我沒有對電梯調度問題整理清思路,將「調度」問題剝離出幾個「子過程」,分割實現,而是一股腦丟進run方法,套在多層循環中實現。在第三次做業中,原本想在Elevator類以外設計operate方法,在Elevator中直接調用,讓代碼更加美觀,惋惜因爲我設計電梯處理請求以及調度問題的思路不夠清晰,沒有實現operate方法從電梯類中分離。