html
先考慮第一個問題:java
數據同步的問題顯然可使用synchronized解決,也就是經典的生產者消費者模型。python
可是因爲初次接觸,對鎖機制理解不清,我還探索了一種不那麼好的方法——volatile,具體使用方法見Whai is volatile算法
它顯然能用在這個問題的解決,可是其實volatile帶來的問題不少,好比性能損失,也不具很好的普適性。設計模式
第二個問題:安全
FAFS在調度策略的實現是很是簡單的,所以更多思考留給瞭如何設計一個良好的架構使得其策略易調整,電梯易拓展等問題。多線程
我在此次的設計中,更多得考慮到了策略的可延伸性。架構
我認爲全部的調度無非就歸結到兩個方面,每一個樓層有無請求,電梯up or down。框架
全部的請求必定是人在肯定的一層上發出的,所以與其考慮維護全部請求隊列,不如維護各個樓層請求。這給調度時候的掃描判斷帶來必定的便利。(後來我發現這樣作有個問題:若是隻維護樓層請求,只針對當前樓層調度,會使調度策略具備侷限性,可能全局考慮才能夠作出更好的優化)oop
一、volatile
(2)Synchronized
相同點:
Ask類是本身封裝的一個請求queue類,主要是解決java自帶Queue類的功能不能徹底知足個人設計需求的問題,這裏設置的flag是爲了exit而特地準備的。
Elevator是電梯類,設計缺陷在於未考慮繼承,調度策略全盤體如今goTo方法中。應該考慮針對openDoor(),closeDoor(),printOut()等電梯基本操做做爲一個父類,針對後續不一樣的電梯進行繼承並補充。第二次做業也考慮到了這個問題。
InputHandler是作輸入處理,是三次做業幾乎一直沿用的一個類
不一樣點:
Elevator 和 inputHandler共享volatile修飾的askList,兩個類均可以操做同步的askList。
Elevator看做producer,inputHandler看做consumer,利用一個Tray類作線程的數據同步。其中使用wait/notifyAll來解決consumer反覆輪詢形成的性能損失,是有利的。
這大概是真正java多線程的入門,第一次做業一直在探索階段,一直在查閱相關的資料,進行了學習傳送門。
這一次算是正式引入了我本身樓層請求的想法和生產者消費者模型。
先來談一談樓層請求的思路:
Floor給每一個樓層設置了askIn,askOut,askQueue隊列,分別儲存該層要求進入的請求,要求出去的請求,全部請求。
input後只需把相應請求put進對應樓層以及總請求隊列
public void put(PersonRequest request) {
...
askQueue.add(request); //總請求隊列
askIn[floorTrans(request.getFromFloor())].add(request); //from floor 的 in 隊列
}
每次到達一個樓層,只需遍歷該樓層的in、out隊列便可(只談Als,不考慮其餘更好的策略)。而且in以後,須要把相應請求給到目的樓層的out隊列,實現的大致框架以下
loop:
arrival();
checkInOut();
FloorAdd();
private boolean checkIn() {
...
long lastDate = new Date().getTime();
while (abs(new Date().getTime() - lastDate) < 400) {
if(ifTake()) { //捎帶判斷
...
askQueue.remove(); //移除
askOut.add(); //相應樓層增長out請求
...
}
}
}
private boolean checkOut() {
...
// scan askOut
printOut();
askOut.remove(); //相應樓層移除out請求
...
}
private int checkInOut() {
checkOut();
checkIn();
}
如此之下,咱們只需(1)設置好mainRequest (2)不斷update loop的最極限樓層 (3)捎帶條件判斷
以後,一個完整的Als電梯就大功告成了。
我的認爲這種樓層請求思路對Als的實現仍是至關友好的(但可能僅限於Als)。
結構安排
Elevator類實現電梯各類最基本操做,開關門,到達...
AlsElevator類繼承基本的電梯類並實現了Als調度算法
inputHandler處理輸入請求。
Tray做爲托盤,協調AlsElevator和inputHandler,進行put,get操做。(生產者消費者模型)
Floor包含全部樓層的in、out請求,放置在托盤中,供輸入線程和電梯線程實時操做。
複雜度分析
複雜度問題主要在於AlsElevator電梯,查看方法複雜度發現:
問題在於
getIn方法:
openDoor()分散在方法中,致使反覆判斷,應該額外操做
反覆遍歷tray中Floor存的隊列
電梯能去的極限樓層的判斷沒有獨立出來。
goTo方法:
樓層的循環採用for而且反覆修改fromFloor 和 toFloor
ifTake方法:
過多過複雜的布爾表達式
解決方案:
取消for循環控制,直接針對各個樓層判斷電梯up or down,放置於run,下降判斷語句使用頻率。
open和in 、out分離,checkIn checkOut只針對是否出入,開關門新設方法控制。
每層設置一個方法判斷電梯極限樓層。
布爾表達式化簡。
修改調度方案,無需過於依賴mainRequest,讓調度接近scan算法,不只能有效減小mainRequest帶來的比較多的判斷,簡化布爾表達式,還能提高性能。
真*重構huozangchang的實例
此次使用了Worker Thread設計模式
此次的設計並很差,因此主要作一個總結和反思
通過思考,本次做業我沒有繼續沿用以前的樓層請求思路(實際上是能夠的),因爲存在拆請求調度等問題,這個思路操做起來相對困難,所以我使用了一種相對比較好實現的思路。
實現思路
(1)把請求分紅兩類,可乘坐電梯直達的和須要一次換乘的。設置request類,用於裝入請求,並在裝入時判斷該請求是否可直達,若是可直達,必須乘坐直達電梯,若是不可直達,在請求被電梯讀取時,選取該電梯能到的最近的換乘點。全部的請求的讀取由多線程電梯本身競爭。
(2)設置調度器分派請求,其中有兩個隊列inputQueue和requestQueue,其中inputQueeu用於與inputHandle交互,requestQueue與電梯交互,因爲電梯運行過程會操做並鎖住請求隊列,爲了防止此時讀入被拒絕,所以再設置一個隊列。只需在調度器中更新requestQueue便可。
(3)電梯類只需實現Als電梯的基本功能,再重寫floorAdd()保證不應停的樓層不停,以及重寫checkOut()針對換乘請求放置新的請求給requestQueue便可。實現較爲簡單。
線程安全
線程安全的保證多是本次設計最大的難點。
線程不安全主要出如今對inputQueue和requestQueue兩個隊列的操做處。
Scheduler中:
(1)put()方法:將請求放置於inputQueue,鎖住inputQueue,保證數據同步。
(2)upgrading()方法:鎖住inputQueue和requestQueue,requestQueue新增請求,inputQueue刪除請求。
(3)get()方法:用於三部電梯獲得請求,鎖住requestQueue,須要刪除requestQueue中的請求。
MoreElevator中:
(1)因爲Als捎帶規則,每次checkIn()若是有人進入須要訪問requestQueue並刪除請求,鎖住。
(2)因爲換乘規則,每次checkOut()若是某請求到達換乘點須要新增requestQueue中的請求,並刪除原請求,須要鎖住requestQueue。
至此,全部線程不安全的問題已經經過synchronized解決了。
複雜度分析
從上述數據能夠發現,
MoreElevator和Request兩個類具備比較高的複雜度。查看相應的方法能夠看出
問題在於:
MoreElevator中:
getIn(),ifTake(),goTo()存在幾乎與第二次做業相同的問題(由於實現幾乎是沿用的)
Request中:
adjustDirection()用於判斷該電梯中的最近換乘點,因爲對A,B,C三種電梯集中判斷,過多的if語句和過複雜的布爾表達式使這個方法複雜度爆掉。
getStop()用於服務adjustDirection(),尋找該請求全部換乘站。也對A,B,C三種電梯集中判斷,過於複雜。
解決方法:
換乘點的判斷和最近換乘點的尋找在裝入相應的電梯時再判斷,避免冗餘。
三次做業只有第三次做業在公測中被發現bug,互測中第三次做業被hack20次(太致命了)
這些bug都有一個相同的報錯RUNTIME ERROR
最後我發現全部測評的cpu時間過長,本身反覆分析無果以後,求助插件,並探索了一套方法。
但願和你們分享、交流,給廣大cpu時間問題的朋友們一個幫助。
以第三次做業爲例
本次出現了較多的cpu時間超時的問題,大量的問題也讓我本身摸索了一條定位,分析和解決cpu time問題的方法。我主要使用VisualVM插件進行分析。
所以決定解決main的cpu時間問題。
因而發現upgrading方法在inputQueue爲空時持續工做,存在暴力輪詢,考慮使用wait/notify進行修改
public void upgrading() {
synchronized (inputQueue) {
while(inputQueue.size() == 0) {
inputQueue.wait();
}
synchronized (requestQueue) {
...
}
inputQueue.notifyAll();
...
}
再作一次抽樣,發現
main線程cpu時間顯著降低。upgrading方法幾乎無cpu時間。
所以,無輸入等待過程cpu時間問題基本解決。
運行過程電梯線程是佔用cpu時間的主力,而且,相比無輸入過程總cpu時間顯著上升,速度較快,也就是說,一旦輸入請求過多,電梯線程對cpu時間的佔用必然致使最後超時,所以定位到了電梯線程的問題。
接下來,查看各方法:
檢查本身的設計發現get方法已是由wait/notify,goTo,floorAdd是普通的循環邏輯不存在暴力輪詢。
接下來,重點就是getIn,checkIn了,觀察數遍後發現,筆者上一次做業遺留下來一個大坑:
private int checkIn (...) {
...
long lastDate = new Date().getTime();
while (abs(new Date().getTime() - lastDate) < 400) {
if ((temp = getIn(hasOpen, from, maxFloor)) != 0) {
....
}
...
暴力輪詢顯而易見。
所以,作了修改
private int checkIn (...) {
...
long lastDate = new Date().getTime();
while (abs(new Date().getTime() - lastDate) < 400) {
if ((temp = getIn(hasOpen, from, maxFloor)) != 0) {
....
}
sleep(long(399.9)) //防止暴力輪詢,先睡一會再起來檢查吧
...
從新CPU抽樣
總CPU時間增長緩慢,電梯線程cpu時間降低,OK了!!!!
到這裏,筆者的CPU time基本控制在一個慢增加,而且穩定在10s之內,問題已經解決。
主要採用了python寫的對拍器和Python寫的隨機樣例生成以及python書寫的簡單輸出結果檢查進行測試。
只在第三次做業發現別人的bug,主要集中在:
一、程序退出錯誤,提早退出問題,須要大量對拍
二、程序退出錯誤,程序沒法退出
三、輸出邏輯問題,某些樓層未關門
四、換乘問題,未成功換乘就中止了
五、cpu_time_error,部分程序存在較爲嚴重的暴力輪詢問題。
1,2,3,4主要經過對拍器發現。
5 主要經過VisualVM運行對應的程序發現。
本次測試與以前最大的差別在於須要定點投放,我本身寫的python程序實現不了,後來經過討論區大佬的方法實現了定點投放。
同時本次增長了cpu_time問題的測試,我藉助了手動運行VisualVM的方法。
線程安全的分析主要關注全部對共享資源的訪問上,在操做共享資源的方法,代碼塊,要考慮是否加鎖的問題。必定要對全部共享資源的訪問作覆蓋性的分析。
多線程設計要關注暴力輪詢的問題,多用wait/notify,儘可能減少CPU時間。
擅用插件分析cpu、內存等狀況。