今天堂妹子給同學們帶來一個實例,一位Java程式設計師的實際工作情況。
實例涉及線程、ReadWriteLoc、synchronized等多個知識點,同時面對需求的溝通與解決,值得同學們收藏。
項目經理今天又接了一個客戶需求,又要折磨我們這些程式設計師屌絲了。這個需求說起來很簡單,做起來非常容易出錯。
先簡單描述一下:
這是一個在線文件編輯器。同一份文件,一個人在讀的時候,其他人不能寫;同理,一個人在寫的時候,其他人也不能讀。也就是說,要麼讀,要麼寫,這兩件事情不能同時進行。
項目經理跟客戶講,「這個很容易實現的,我們是可以做的。」。什麼都可以做,做不出來說是我們程式設計師能力不行,他一點責任都沒有。領導發話了,不管怎麼樣,事情還是要做的。
看了一下需求,有兩個問題,我得先問清楚,否則到時候做得不對,他又把負責推給我,我們項目經理經常搞這些讓我背黑鍋的事情。
「多人同時讀可以嗎?」
「當然可以啦!多少人來讀都沒關係,文件的內容不要變就行。」。
「多人同時寫可以嗎?」
「當然不行啦!你寫別人也會寫,文件不知道以哪份數據為準了。」。
他態度極其惡劣,算了,不跟他計較了,我的項目獎金還在他手裡。趕緊完工,下班了還要回家抱小孩。
根據多年的項目實戰經驗,我寫了一個超牛逼的 Data 類,來封裝文件的數據。看起來是這樣的:
稍微解釋一下:
Data 類中封裝了一個 char 數組類型的 buffer 成員變量。
在構造器中傳入一個 size,表示 buffer 的長度,並在其中創建並初始化這個 buffer,使其每個字符都為「*」。
提供兩個方法,一個負責讀取,另一個負責寫入。在讀取方法中只需遍歷 buffer,將結果不斷 append 到一個 StringBuilder 中,最終將其轉為 String 並返回。
在寫入方法中傳入一個字符,仍然是遍歷 buffer,賦值 buffer 中的每個字符,這樣可以使 buffer 中每個字符都是相同的。
故意在讀寫方法中加入了一個 sleep() 方法,讓程序運行慢一點,模擬比較耗時的操作。而且故意讓寫入比讀取慢一點,因為將 sleep() 方法放入了 write() 方法的循環體中,而 read() 方法卻沒有。
當然了,以上這個示例跑通了,我想項目經理那個需求也不難實現。這也是我們平時做開發的一種習慣,先快速地寫個 Demo 出來,讓領導們看看,技術上走通了,我們再實現具體的需求。
好了,不就是要同時讀寫嗎?這不就是一個典型的多線程使用場景嗎?於是我快速地寫了一個讀取線程,讓它拼命地去讀取 Data 中的數據。
在 ReaderThread 中通過一個死循環去不斷地讀取 Data 中的數據,並將結果列印出來。
再來一個寫入線程,讓它使勁地向 Data 中寫入數據。
一次性可以傳入一個字符串到 WriterThread 中,它將不斷獲取下一個字符(請見 next() 方法),並將該字符寫入 Data 中。
如果讓 ReaderThread 與 WriterThread 同時工作會怎樣?不妨寫了一個簡單的 Client 類運行試試看。
我開啟了 5 個 ReaderThread 與 2 個 WriterThread,模擬讀得多寫得少的情況,並將不同的數據寫入 Data 中。
運行一下!
…
Thread-1 => AA0A0A00A0
Thread-4 => AA0A0A00A0
Thread-3 => AA0A0A00A0
Thread-2 => AA0A0A00A0
Thread-0 => AA0A0A00A0
…
為何每次讀取出來的數據不一致呢?應該是輸出 10 個相同的字符才對啊!Data 的 buffer 中每個字符不是應該相同嗎?
如果把這個結果給項目經理看,他肯定要搞死我的。
哦!想到了!在多線程開發中,資源的訪問一定要做到「共享互斥」,也就是說要「上鎖」,這招還是架構師前幾天才教我的,我怎能不用?
於是我用了 Java 多線程中超牛逼的 synchronized 關鍵字,將它放到了 read() 與 write() 方法上,這樣就可以保證 synchronized 方法在同一時刻只能被一個線程調用了,其他線程將會阻擋在外。
廢話少說,趕緊加兩個 synchronized 運行看看吧。
再運行一把!
…
Thread-0 => 1111111111
Thread-4 => CCCCCCCCCC
Thread-3 => CCCCCCCCCC
Thread-2 => CCCCCCCCCC
Thread-1 => CCCCCCCCCC
…
終於搞定啦!這下子項目經理應該滿意了吧?
「不錯!這效果很好啊,同時寫同時讀,而且每次讀出來的數據都一樣,技術上應該是走通了,這個需求應該可以實現了吧?」 項目經理問。
「沒問題啊!小意思!」 我高興的答。
「這是一個在線文件編輯器,你考慮過性能問題嗎?」 架構師突然問了一句。
「性能很好啊!」
「你可以在 ReaderThread 中每調用 10 次 read() 方法,就列印 1 次所耗時間看看。」
「好啊!」
這還不簡單,我快速地給 ReaderThread 的 run() 方法中加了幾行代碼,測試一下運行所消耗的時間。
跑起來吧!
…
Thread-2 => IIIIIIIIII
Thread-2 — 24802ms
Thread-3 => IIIIIIIIII
Thread-3 — 24901ms
Thread-4 => IIIIIIIIII
Thread-4 — 25001ms
Thread-0 => 3333333333
…
Thread-0 => 1111111111
Thread-0 — 55305ms
Thread-4 => CCCCCCCCCC
Thread-3 => CCCCCCCCCC
Thread-2 => CCCCCCCCCC
Thread-1 => CCCCCCCCCC
Thread-1 — 58705ms
Thread-2 => CCCCCCCCCC
…
我隨意挑選了其中這 5 個 ReaderThread 所消耗的時間,平均值是:37742.8 毫秒,折合 37.8 秒。
我心裡也沒譜了,這性能到底是否需要優化呢?於是我帶著測試結果,去向架構師請教。
他看到了這樣的結果,微笑著搖了搖頭。從他鄙視而又猥瑣的表情上,我可以推測,這次他又要在我面前露一手了。
來吧,我給你寫一個 ReadWriteLock,你自己去看吧。
隨後,架構師用他熟練的手指,瘋狂地在鍵盤上敲了一堆讓我一知半解的東西。
我看出來了,架構師特意寫了很多注釋,免得我總是去煩他。
代碼不解釋了,看看注釋吧,有疑問可以給我留言哦!
此時,Data 類還需要稍作改寫。
同樣的 Client 類,我再運行一把試試看,性能是否有提高呢?
…
Thread-1 => 4444444444
Thread-2 — 14000ms
Thread-0 — 14001ms
Thread-3 — 14000ms
Thread-4 — 14000ms
Thread-1 — 14001ms
Thread-4 => IIIIIIIIII
…
平均下來是 14000.4 毫秒,折合 14.0 秒,比以前快了 63%,而且輸出的結果都比以前平穩(以前忽高忽低的)。
果然是架構師,真讓我們這些程式設計師崇拜啊!
最後架構師過來,看到我在那裡得意地笑。他拍拍我的肩,對我說:「別樂了,其實 JDK 1.5 中已經有 ReadWriteLock 了,我這個只不過是一個精簡版而已,去看看 java.util.concurrent.locks.ReadWriteLock 吧,你一定會震精!」。
看來我真是孤陋寡聞啊,打開 JDK API 看到了 ReadWriteLock:
可以通過 ReadWriteLock 接口來獲取 ReadLock 與 WriteLock,它們都是 Lock 對象,這也是一個接口。
官方提供了一個 ReadWriteLock 接口的實現類 java.util.concurrent.locks.ReentrantReadWriteLock。
該接口中,有兩個非常重要的方法:lock() 與 unlock(),分別表示「上鎖」與「解鎖」。
嘗試用一下 JDK 的 ReadWriteLock 吧。
再次運行一把看看效果。
使用了 JDK 的 ReadWriteLock,性能與自己實現的 ReadWriteLock 差不多,大家不妨自己試一下吧。
此外 JDK 還提供了一個更加簡單的 ReentrantLock,它可以取代 synchronized,確保獲取更高的吞吐率,一般可以這樣來做:
以前的做法:
現在的做法:
這裡提供兩張 synchronized 與 Lock 的性能測試對比:
總結
當系統中出現不同的讀寫線程同時訪問某一資源時,需要考慮共享互斥問題,可使用 synchronized 解決次問題。若對性能要求較高的情況下,可考慮使用 ReadWriteLock 接口及其 ReentrantReadWriteLock 實現類,當然,自己實現一個 ReadWriteLock 也是一種解決方案。此外,為了在高並發情況下獲取較高的吞吐率,建議使用 Lock 接口及其 ReentrantLock 實現類來替換以前的 synchronized 方法或代碼塊。
以這個實例為拋磚引玉,同學們自己在學習過程或工作過程中,是如何處理的,值得同學們思考。
尚學堂Java培訓開課時間表
西安校區
Android高薪就業班:
【脫產班】開課時間:已開班 搶座中..
【周末班】開課時間:已開班 搶座中..
電話:029-62258374 029-62326079
地址:陝西省西安市高新區科技二路西安軟體園天澤大廈五樓
鄭州校區
Android全程就業班
【脫產班】 開課時間:2017年03月28日 搶座中..
電話:0371-58500958
地址:河南省鄭州市金水區東風路與文化路交叉南100米
什麼是脫產班:
脫產班就是參加工作後再去校內進行學習
一種學習方式。其管理模式與高校普通班
級一樣,學習期間完全脫離生產不在原單
位工作,全身心的進行全日制學習。不佔
用學生周六和周日的工作時間,對學生有
正常的、相對固定的授課教室和管理要求
,有穩定的寒暑假安排。
一個有用的公眾號
尚學堂java培訓
Android
web前端
培訓基地
親愛的同學們:
尚學堂java培訓開班前有免費試聽課程,想學習又不太確定的同學們趕緊聯繫老師,獲取免費試聽名額(每個班級的名額是有限的哦,速聯速得)。
獲取方式:打開【尚學堂java培訓】公眾號,點擊-精品課程-我要試聽-聯繫老師獲取免費試聽名額;或直接聯繫公眾號,讓「萬能」的小編為您解決一切問題!