2023年一起來認識一下StampedLock吧
導讀 | 本文主要講解了StampedLock的功能和使用,至於原理,StampedLock雖然不像其它鎖一樣定義了內部類來實現AQS框架,但是StampedLock的基本實現思路還是利用CLH佇列進行執行緒的管理,透過同步狀態值來表示鎖的狀態和型別,具體的原始碼實現大家感興趣的自己可以追蹤看看。 |
想到讀寫鎖,大家第一時間想到的可能是ReentrantReadWriteLock。實際上,在jdk8以後,java提供了一個效能更優越的讀寫鎖併發類StampedLock,該類的設計初衷是作為一個內部工具類,用於輔助開發其它執行緒安全元件,用得好,該類可以提升系統效能,用不好,容易產生死鎖和其它莫名其妙的問題。本文主要和大家一起學習下StampedLock的功能和使用。
StampedLock的狀態由版本和模式組成。鎖獲取方法返回一個戳,該戳表示並控制對鎖狀態的訪問。StampedLock提供了3種模式控制訪問鎖:
獲取寫鎖,它是獨佔的,當鎖處於寫模式時,無法獲得讀鎖,所有樂觀讀驗證都將失敗。
- writeLock(): 阻塞等待獨佔獲取鎖,返回一個戳, 如果是0表示獲取失敗。
- tryWriteLock():嘗試獲取一個寫鎖,返回一個戳, 如果是0表示獲取失敗。
- long tryWriteLock(long time, TimeUnit unit): 嘗試獲取一個獨佔寫鎖,可以等待一段事件,返回一個戳, 如果是0表示獲取失敗。
- long writeLockInterruptibly(): 試獲取一個獨佔寫鎖,可以被中斷,返回一個戳, 如果是0表示獲取失敗。
- unlockWrite(long stamp):釋放獨佔寫鎖,傳入之前獲取的戳。
- tryUnlockWrite():如果持有寫鎖,則釋放該鎖,而不需要戳值。這種方法可能對錯誤後的恢復很有用。
long stamp = lock.writeLock(); try { .... } finally { lock.unlockWrite(stamp); }
悲觀的方式後去非獨佔讀鎖。
long stamp = lock.readLock(); try { .... } finally { lock.unlockRead(stamp); }
樂觀讀也就是若讀的操作很多,寫的操作很少的情況下,你可以樂觀地認為,寫入與讀取同時發生機率很少,因此不悲觀地使用完全的讀取鎖定,程式可以檢視讀取資料之後,是否遭到寫入執行的變更,再採取後續的措施(重新讀取變更資訊,或者丟擲異常) ,這一個小小改進,可大幅度提高程式的吞吐量。
StampedLock支援 tryOptimisticRead()方法,讀取完畢後做一次戳校驗,如果校驗透過,表示這期間沒有其他執行緒的寫操作,資料可以安全使用,如果校驗沒透過,需要重新獲取讀鎖,保證資料一致性。
long stamp = lock.tryOptimisticRead(); // 驗戳 if(!lock.validate(stamp)){ // 鎖升級 }
此外,StampedLock 提供了api實現上面3種方式進行轉換:
如果鎖狀態與給定的戳記匹配,則執行以下操作之一。如果戳記表示持有寫鎖,則返回它。或者,如果是讀鎖,如果寫鎖可用,則釋放讀鎖並返回寫戳記。或者,如果是樂觀讀,則僅在立即可用時返回寫戳記。該方法在所有其他情況下返回零
如果鎖狀態與給定的戳記匹配,則執行以下操作之一。如果戳記表示持有寫鎖,則釋放它並獲得讀鎖。或者,如果是讀鎖,返回它。或者,如果是樂觀讀,則僅在立即可用時才獲得讀鎖並返回讀戳記。該方法在所有其他情況下返回零。
如果鎖狀態與給定的戳記匹配,那麼如果戳記表示持有鎖,則釋放它並返回一個觀察戳記。或者,如果是樂觀讀,則在驗證後返回它。該方法在所有其他情況下返回0,因此作為“tryUnlock”的形式可能很有用。
下面用一個例子演示下StampedLock的使用,例子來源jdk中的javadoc。
@Slf4j @Data public class Point { private double x, y; private final StampedLock sl = new StampedLock(); void move(double deltaX, double throws{ //涉及對共享資源的修改,使用寫鎖-獨佔操作 long stamp = sl.writeLock(); log.info("writeLock lock success"); Thread.sleep(500); try { x += deltaX; y += deltaY; } finally { sl.unlockWrite(stamp); log.info("unlock write lock success"); } } /** * 使用樂觀讀鎖訪問共享資源 * 注意:樂觀讀鎖在保證資料一致性上需要複製一份要操作的變數到方法棧,並且在運算元據時候可能其他寫執行緒已經修改了資料, * 而我們操作的是方法棧裡面的資料,也就是一個快照,所以最多返回的不是最新的資料,但是一致性還是得到保障的。 * * @return */ double distanceFromOrigin() throws{ long stamp = sl.tryOptimisticRead(); // 使用樂觀讀鎖 log.info("tryOptimisticRead lock success"); // 睡一秒中 Thread.sleep(1000); double currentX = x, currentY = y; // 複製共享資源到本地方法棧中 if (!sl.validate(stamp)) { // 如果有寫鎖被佔用,可能造成資料不一致,所以要切換到普通讀鎖模式 log.info("validate stamp error"); stamp = sl.readLock(); log.info("readLock success"); try { currentX = x; currentY = y; } finally { sl.unlockRead(stamp); log.info("unlock read success"); } } return Math.sqrt(currentX * currentX + currentY * currentY); } void moveIfAtOrigin(double newX, double{ // upgrade // Could instead start with optimistic, not read mode long stamp = sl.readLock(); try { while (x == 0.0 && y == 0.0) { long ws = sl.tryConvertToWriteLock(stamp); //讀鎖轉換為寫鎖 if (ws != 0L) { stamp = ws; x = newX; y = newY; break; } else { sl.unlockRead(stamp); stamp = sl.writeLock(); } } } finally { sl.unlock(stamp); } } }
測試用例:
@Test public void testStamped() throws InterruptedException { Point point = new Point(); point.setX(1); point.setY(2); // 執行緒0 執行了樂觀讀 Thread thread0 = new Thread(() -> { try { // 樂觀讀 point.distanceFromOrigin(); } catch (InterruptedException e) { e.printStackTrace(); } }, "thread-0"); thread0.start(); Thread.sleep(500); // 執行緒1 執行寫鎖 Thread thread1 = new Thread(() -> { // 樂觀讀 try { point.move(3, 4); } catch (InterruptedException e) { e.printStackTrace(); } }, "thread-1"); thread1.start(); thread0.join(); thread1.join(); }
結果:
效能對比
正是由於StampedLock的樂觀讀模式,早就StampedLock的高效能和高吞吐量,那麼具體的效能提高有多少呢?
下圖是和ReadWritLock相比,在一個執行緒情況下,讀速度是其4倍左右,寫是1倍。
下圖是16個執行緒情況下,讀效能是其幾十倍,寫效能也是近10倍左右:
下圖是吞吐量提高:
那麼這樣是不是說StampedLock可以全方位的替代ReentrantReadWriteLock, 答案是否定的,StampedLock相對於ReentrantReadWriteLock有下面兩個問題:
所以最終選擇StampedLock還是ReentrantReadWriteLock,還是要看具體的業務場景。
本文主要講解了StampedLock的功能和使用,至於原理,StampedLock雖然不像其它鎖一樣定義了內部類來實現AQS框架,但是StampedLock的基本實現思路還是利用CLH佇列進行執行緒的管理,透過同步狀態值來表示鎖的狀態和型別,具體的原始碼實現大家感興趣的自己可以追蹤看看。
原文來自:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69955379/viewspace-2933660/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Vue會了嗎?來認識一下React吧(上)VueReact
- 1.嘿,我們來認識一下vue吧!Vue
- 一起來解讀LIS吧!
- 來入門一下kotlin吧Kotlin
- 認識一下 Mobx
- 來一起寫一個跳錶吧
- 前端er來學習一下webWorker吧前端Web
- 一起來學習樹鏈剖分吧!
- 跟我一起學docker(一)--認識Docker
- Flutter學習指南App,一起來玩Flutter吧~FlutterAPP
- 一起淺淺認識 Linux 系統Linux
- React系列之一起認識Render PropReact
- 認識一下HTTP抓包工具FiddlerHTTP
- PHP-認識一下系統常量PHP
- 瀏覽器的event loop,一起來了解下吧瀏覽器OOP
- 一起來認識《最終幻想 14》的兩位「光之裝潢師」
- 一起努力吧,2023
- 如果你也剛入門React,來一起學習吧React
- 一起來梳理JVM知識點JVM
- 一起來作畫吧「GitHub 熱點速覽 v.22.14」Github
- 認識一下JavaScrip中的超程式設計Java程式設計
- 帶你漲姿勢的認識一下 KafkaKafka
- 認識一下Flutter中Navigator資料傳遞原理Flutter
- java基礎之執行緒 認識一下synchronizeJava執行緒
- 進階MAC大神,只差這一步之遙!一起來學吧!Mac
- PHPer必學:這幾個composer命令要認識一下!PHP
- 資料安全認證你有幾個?來了解一下
- 我也來總結一下nginx知識點Nginx
- 提筆忘字?《夢蝶》手遊12月29日首測來襲,一起來檢驗下吧!
- DV SSL證書是怎麼誕生的?一起來了解一下
- [續更]一起來擼一下Flex佈局裡面的那些屬性Flex
- 來吧,一起給小程式原生的swiper化個妝,讓她變成更漂亮點的banner吧(基於mpvue)Vue
- 簡單的瞭解一下AQS吧AQS
- 我寫了個BoardView,看一下吧。View
- 一起認識下,那些我們應該知道的mysql中的變數知識點MySql變數
- 認識一下,我們是應用社交「幕後大佬」 IM 家族
- 如何管理你的狀態列圖示?只需2招、快來get一下吧!
- 來吧,聊聊API安全API