上週在由Heinz Kabutz經過JCrete 組織的開放空間會議(unconference)上,我參加一個新的java規範 JSR166 StampedLock 的審查會議。 StampedLock 是爲了解決多個readers 併發訪問共享狀態時,系統出現的內存地址競爭問題。在設計上經過使用樂觀的讀操做, StampedLock
比 ReentrantReadWriteLock 更加高效;html
在會議期間,我忽然意思到兩點:java
爲了比較不一樣的實現方式,我須要採用一種不偏向任意一方的API測試用例。 好比:API必須不產生垃圾、而且容許方法是原子性的。一個簡單的測試用例是設計一個可在兩維空間中移動其位置的太空船,它位置的座標能夠原子性的讀取;每一次事物裏至少須要讀寫2個域,這使得併發變得很是有趣;git
/** * 併發接口,表示太空船能夠在2維的空間中移動位置;而且同時更新讀取位置 */ public interface Spaceship { /** * 讀取太空船的位置到參數數組 coordinates 中 * * @param coordinates 保存讀取到的XY座標. * @return 當前的狀態 */ int readPosition(final int[] coordinates); /** * 經過增長XY的值表示移動太空船的位置。 * * @param xDelta x座標軸上移動的增量. * @param yDelta y座標軸上移動的增量. * @return the number of attempts made to write the new coordinates. */ int move(final int xDelta, final int yDelta); }
上面的API經過分解一個不變的位置對象,自己是乾淨的。可是我想保證它不產生垃圾,而且須要能最直接的更新多個內容域。這個API能夠很容易地擴展到三維空間,並實現原子性要求。github
爲每個飛船都設置多個實現,而且做爲一個測試套件。本文中全部的代碼和結果均可以在這裏找到。算法
該測試套件會依次運行每一種實現.而且使用 megamorphic dispatch模式,防止併發訪問中的方法內聯(inlining),鎖粗化(lock-coarsening),循環展開(loop unrolling)的問題;segmentfault
每種實現都執行下面4個不一樣的線程的狀況,結果也是不一樣的;api
1 reader – 1 writer 2 readers – 1 writer 3 readers – 1 writer 2 readers – 2 writers
全部的測試運行在64位機器、Java版本:1.7.0_2五、 Linux版本:3.6.30、4核 2.2GHz Ivy Bridge (第三代Core i系列處理器)i7-3632QM的環境上。數組
測試吞吐量的時候,是經過每一種實現都重複測試超過5次,每一次都運行5秒以上,以保證系統足夠預熱,下面的結果都是第5次以後平均每秒吞吐量。爲了更像一個典型的java應用;沒有采用會致使明顯減小差別的線程依附性(thread affinity)和多核隔離(core isolation )技術;併發
上述圖表的原始數據能夠在這裏找到oracle
結果裏面真正令我吃驚的是ReentrantReadWriteLock的性能,我沒有想到的是,在這樣的場景下它在讀和少許寫之間取得的巨大的平衡性,
我主要的收穫:
StampedLock 對現存的鎖實現有巨大的改進,特別是在讀線程愈來愈多的場景下:
StampedLock有一個複雜的API,對於加鎖操做,很容易誤用其餘方法;
當只有2個競爭者的時候,Synchronised是一個很好的通用的鎖實現;
當線程增加可以預估,ReentrantLock是一個很好的通用的鎖實現;
選擇使用ReentrantReadWriteLock時,必須通過當心的適度的測試;全部重大的決定,必須在基於測試數據的基礎上作決定;
無鎖的實現比基於鎖的算法有更好短吞吐量;
很是開心能看到無鎖技術對基於鎖的算法的影響; 樂觀鎖的策略,實際上就是一個無鎖算法技術。
以個人經驗看,教學和開發中的無鎖算法,不只能顯著改善吞吐量;同時他們也提供更低的延遲。
原文 Lock based vs lock free concurrent
翻譯 曹姚君 校對 方騰飛
via ifeve