OO第二單元電梯總結 架構模式 Hw5, Hw6, Hw7三次作業架構基本沒有巨大變化,屬於增量的疊加開發 hw5 一級生產者消費者模型with策略類分離 第一次作業, 我做了兩種架構的嘗試, 寫了: 調度線程祭天型 單托盤 帶調度器線程的兩級托盤 在嘗試寫了兩種架構的基礎上, 我分析了一下兩種架構 ...
OO第二單元電梯總結
目錄架構模式
Hw5, Hw6, Hw7三次作業架構基本沒有巨大變化,屬於增量的疊加開發
hw5 一級生產者消費者模型with策略類分離
第一次作業, 我做了兩種架構的嘗試, 寫了:
-
調度線程祭天型 單托盤
-
帶調度器線程的兩級托盤
在嘗試寫了兩種架構的基礎上, 我分析了一下兩種架構,
-
不對任何數據進行區分, 所有數據對電梯可見
-
是二級生產者消費者模型, 僅僅對每個樓進行劃分,每棟樓之間的請求隔離, 樓內請求電梯間是互通的
在我的兩種架構里, 第二種架構的scheduler
線程僅僅負責取總托盤的請求,然後按樓區分發。本質上和你電梯直接去查找大托盤, 各取所需, 在功能上似乎沒有任何區別。 反而調度祭天,減少了一個線程, 首先會簡單不少, 其次, 每個電梯可見所有請求, 或許擴展性反而更好, 所以最終我選擇了1作為三次作業的基本架構。
總之,對於架構的選擇取捨就是:
要獲取高效簡潔解決問題的架構, 就得在調度自由度和懶癌(bushi, 簡單度之間做取捨
————沃茲基朔德
關於策略類的提取:
體現了DIP依賴倒置原則高級模塊不應該依賴低級模塊,而是依賴抽象介面,通過抽象介面使用對應的低級模塊
關於優化實現了:
-
量子電梯, 通過記錄上次arrive & close 時間, 減少了wait以後,再被喚醒,可以快速的度過本層(其實就是拿一部分沉睡時間當作運動中的時間)比如:
private void moveArrive() { try { Thread.sleep(max(400 + (lastArriveTime - System.currentTimeMillis()), 0)); // Thread.sleep(400); } catch (InterruptedException e) { e.printStackTrace(); } building = (building + dir + 5) % 5; // arrive MyOutput.println("ARRIVE-" + buildingNameList[building] + "-" + floor + "-" + elevatorId); }
-
開關門400ms恆定保持
-
先下後上,儘量哆啦人(這不是共識嗎?但我和其他童鞋交流時發現他沒有,姑且算是吧
hw6 增加了兩個電梯子類的一級生產者消費者模型
這次作業中, 可以看到最令人神奇的就是:
- 加入了橫向電梯
- 動態新增電梯
- 不考慮換乘
這就意味著我們要為環形電梯設計新的的調度策略, 同時多個電梯這一次終於可以互相進行搶人, 這就帶來了競爭和分配如何選擇的問題
-
對於新增橫豎電梯,還存在行為上的差異, 所以我搞了兩個子類, 分別處理橫豎方向的電梯
-
動態新增電梯, 這裡其實暗示我們搞線程池, 然鵝我也現在才發現, 如果以後有機會可以搞個線程池, 其次由於選擇了調度線程寄了天, 自然選擇
-
提前為換乘打餘量,我將托盤中的
PersonRequest
改成了ConcurrentLinkedQueue<PersonRequest>
鏈表,具體怎麼換乘hw7來說 -
環形電梯調度策略:
採用了類ALS基準策略, 效果還可以, 基本性能分都拿到了, 98+不戳的
Look也寫了,但是擔心出bug, 所以沒有採取這個策略對象, 用了ALS, 當然Look肯定寫好了是更快的
hw7 帶換乘的一級生產者消費者模型
可以看到基本沒改什麼, 就是在Input類中加入了一個換乘拆分函數
線程的協作簡圖
主要就是
- 輸入線程,接收到輸出, 進行請求拆分, 或者創建電梯線程
- 電梯線程, 在while迴圈中, 每次檢測是否wait或者return, 此後決定上下人,並依據上下人開關門, 最後決定方向並前進
同步塊和鎖
總結分析三次作業中同步塊的設置和鎖的選擇,並分析鎖與同步快中處理語句之間的關係
三次作業架構基本保持了一致, 且同步塊基本也保持了一致, 取最後一次作業的同步塊講解:
首先是 RequestTable.java中
// InputHandler 添加請求, Elevator送人到中轉站時
public synchronized void addRequest(ConcurrentLinkedQueue<PersonRequest> a) {
requests.add(a);
}
// personLeft維護的是 沒有送達目的地的人數
// InputHandler接收到請求時
public synchronized void addPersonLeft() {
this.notifyAll();
personLeft++;
}
// Elevator送人到目的地
public synchronized void subPersonLeft() {
personLeft--;
}
// 輸入結束
public synchronized boolean isInputEnd() {
return inputEnd;
}
// 輸入結束
public synchronized void setInputEnd(boolean inputEnd) {
this.notifyAll();
this.inputEnd = inputEnd;
}
// 上下是否沒有請求了
public synchronized boolean isUpDownEmpty(String buildingName) {
...
}
// 同理
public synchronized boolean isLeftRightEmpty(int floor) {
...
}
// 前方是否有請求
public synchronized boolean upDownRequestAhead(String buildingName, int floor, int velocity) {
...
}
// 人都送到了
public synchronized boolean noPersonLeft() {
return personLeft == 0;
}
Strategy類木有鎖, 不互斥的給出運動方向、上下人
public interface Strategy {
public Vector<PersonRequest> getPickUpRequests(Vector<ConcurrentLinkedQueue<PersonRequest> inside,
int floor, int dir, int capLeft);
public boolean turnAround(Vector<ConcurrentLinkedQueue<PersonRequest> inside, int floor, int dir, int capacity);
}
InputHandler中的鎖:
// 用於判停,判wait
private boolean checkWaitReturn() {
synchronized (requestTable) {
while (this.isempty() && requestTable.isLeftRightEmpty(floor)) {
if (requestTable.isInputEnd() &&
requestTable.isLeftRightEmpty(floor) && requestTable.noPersonLeft()
&& this.isempty()) {
requestTable.notifyAll();
return true;
}
try {
requestTable.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
return false;
}
主要就是鎖住了RequestTable 防止 check & modify, read & write, write&write 的線程危險, 使得其互斥。
還有就是電梯在運行時需要查看一下情況,其實也可以放在RequestTable類里。
調度器設計
hw5的一個版本里, 我的調度器主要就是負責按樓層分發, 沒有任何特殊性,感覺名存實亡就是擺設, 所以就捨棄了調度器線程。
我的調度器線程沒了, 調度主要就是自由競爭。
同時換乘請求拆分時, 優化了一部分基準策略, 按概率隨機分配拆分樓層, 使得拆分樓層分佈保持較為均勻
bug與測試 總結
強測&互測bug
三次作業總共出現了一個bug, 就是我第一次作業最後提交的時候一著急, 不小心把上下人邏輯搞錯了, 導致可以承載的人數過多,wa了好幾個點,真的心痛, 血的教訓。
debug最重要的是按部就班的按debug流程來, 不要著急,不能東一榔頭西一棒槌,都可能錯過你的bug.
ps: 中測真的很弱, 感覺就是把你騙進來殺
————沃茲基朔德
比如在那以後我就規定了這麼一套流程,防止我心急或者漏了什麼,導致bug被miss過去, 看著圖一樂吧,畢竟我真是太粗了, 這些流程守好了, 別丟東西了
遇到的記憶深刻的bug
本地測試與測評機
測評機三次作業都做了, 還和小伙伴一塊分享一塊測試, 真不戳!評測機也是不斷迭代hhh
從最開始只能測一個人,到支持多個人一起pk速度
唯一不足就是對於超時應該及時掐斷, 我還沒來及整, python多線程有時間學一波吧
心得體會
線程安全心得
線程安全的心得在於,關鍵在於設計線程安全類, 通過線程安全類來保證各個線程的行為是安全的, 比如通過鎖的方法保證互斥, 通過資源也同時完成了線程間的協作通信。 關鍵在於選取合適的臨界區, 保證線程安全的情況下儘量小。
層次化設計&設計原則
三次作業基本是迭代的, 每次作業都在上次的作業上或是繼承, 或是新增了一些功能, 較好的實現了代碼復用
SRP: 職責應該單一,不要承擔過多的職責。體現在每個類只負責自己的行為, 比如:
電梯只負責開關門, 上下人, 停止, 轉向, 前進。
RequestTable僅僅負責,根據電梯的信息進行搜索並返回數據,以及數據的更刪查改。
InputHandler僅僅負責拿到請求、處理請求
OCP:修改軟體功能的時候,使得他不能修改我們原有代碼,只能新增代碼實現軟體功能修改的目的。 可以從三次代碼的結構是迭代的, 僅僅新增了部分代碼, 實現代碼復用。
ISP: 介面的內容一定要儘可能地小,能有多小就多小。 我將策略類的介面僅設置了2個方法,轉向、上下人,最小限度的保持了策略類對電梯的控制
LSP, DIP: 體現在策略類的分離體現了 ,前面說過了,就不重覆了
真情實感
做首詩紀念一下
電梯月
——cywuuuu
歡送電梯月,
可惜有bug。
下次再努力,
爭取bugfree!