都已經(jīng)2023年了,你還不知道StampedLock嗎?
?概述
想到讀寫鎖,大家第一時(shí)間想到的可能是ReentrantReadWriteLock?。實(shí)際上,在jdk8以后,java提供了一個(gè)性能更優(yōu)越的讀寫鎖并發(fā)類StampedLock?,該類的設(shè)計(jì)初衷是作為一個(gè)內(nèi)部工具類,用于輔助開發(fā)其它線程安全組件,用得好,該類可以提升系統(tǒng)性能,用不好,容易產(chǎn)生死鎖和其它莫名其妙的問題。本文主要和大家一起學(xué)習(xí)下StampedLock的功能和使用。
StampedLock介紹
StampedLock?的狀態(tài)由版本和模式組成。鎖獲取方法返回一個(gè)戳,該戳表示并控制對(duì)鎖狀態(tài)的訪問。StampedLock提供了3種模式控制訪問鎖:
寫模式
獲取寫鎖,它是獨(dú)占的,當(dāng)鎖處于寫模式時(shí),無法獲得讀鎖,所有樂觀讀驗(yàn)證都將失敗。
- writeLock(): 阻塞等待獨(dú)占獲取鎖,返回一個(gè)戳, 如果是0表示獲取失敗。
- tryWriteLock():嘗試獲取一個(gè)寫鎖,返回一個(gè)戳, 如果是0表示獲取失敗。
- long tryWriteLock(long time, TimeUnit unit): 嘗試獲取一個(gè)獨(dú)占寫鎖,可以等待一段事件,返回一個(gè)戳, 如果是0表示獲取失敗。
- long writeLockInterruptibly(): 試獲取一個(gè)獨(dú)占寫鎖,可以被中斷,返回一個(gè)戳, 如果是0表示獲取失敗。
- unlockWrite(long stamp):釋放獨(dú)占寫鎖,傳入之前獲取的戳。
- tryUnlockWrite():如果持有寫鎖,則釋放該鎖,而不需要戳值。這種方法可能對(duì)錯(cuò)誤后的恢復(fù)很有用。
讀模式
悲觀的方式后去非獨(dú)占讀鎖。
- readLock(): 阻塞等待獲取非獨(dú)占的讀鎖,返回一個(gè)戳, 如果是0表示獲取失敗。
- tryReadLock():嘗試獲取一個(gè)讀鎖,返回一個(gè)戳, 如果是0表示獲取失敗。
- long tryReadLock(long time, TimeUnit unit): 嘗試獲取一個(gè)讀鎖,可以等待一段事件,返回一個(gè)戳, 如果是0表示獲取失敗。
- long readLockInterruptibly(): 阻塞等待獲取非獨(dú)占的讀鎖,可以被中斷,返回一個(gè)戳, 如果是0表示獲取失敗。
- unlockRead(long stamp):釋放非獨(dú)占的讀鎖,傳入之前獲取的戳。
- tryUnlockRead():如果讀鎖被持有,則釋放一次持有,而不需要戳值。這種方法可能對(duì)錯(cuò)誤后的恢復(fù)很有用。
樂觀讀模式
樂觀讀也就是若讀的操作很多,寫的操作很少的情況下,你可以樂觀地認(rèn)為,寫入與讀取同時(shí)發(fā)生幾率很少,因此不悲觀地使用完全的讀取鎖定,程序可以查看讀取資料之后,是否遭到寫入執(zhí)行的變更,再采取后續(xù)的措施(重新讀取變更信息,或者拋出異常) ,這一個(gè)小小改進(jìn),可大幅度提高程序的吞吐量。
StampedLock?支持 tryOptimisticRead()方法,讀取完畢后做一次戳校驗(yàn),如果校驗(yàn)通過,表示這期間沒有其他線程的寫操作,數(shù)據(jù)可以安全使用,如果校驗(yàn)沒通過,需要重新獲取讀鎖,保證數(shù)據(jù)一致性。
- tryOptimisticRead(): 返回稍后可以驗(yàn)證的戳記,如果獨(dú)占鎖定則返回零。
- boolean validate(long stamp): 如果自給定戳記發(fā)行以來鎖還沒有被獨(dú)占獲取,則返回true。
此外,StampedLock 提供了api實(shí)現(xiàn)上面3種方式進(jìn)行轉(zhuǎn)換:
- long tryConvertToWriteLock(long stamp)
如果鎖狀態(tài)與給定的戳記匹配,則執(zhí)行以下操作之一。如果戳記表示持有寫鎖,則返回它。或者,如果是讀鎖,如果寫鎖可用,則釋放讀鎖并返回寫戳記?;蛘撸绻菢酚^讀,則僅在立即可用時(shí)返回寫戳記。該方法在所有其他情況下返回零
- long tryConvertToReadLock(long stamp)
如果鎖狀態(tài)與給定的戳記匹配,則執(zhí)行以下操作之一。如果戳記表示持有寫鎖,則釋放它并獲得讀鎖。或者,如果是讀鎖,返回它?;蛘撸绻菢酚^讀,則僅在立即可用時(shí)才獲得讀鎖并返回讀戳記。該方法在所有其他情況下返回零。
- long tryConvertToOptimisticRead(long stamp)
如果鎖狀態(tài)與給定的戳記匹配,那么如果戳記表示持有鎖,則釋放它并返回一個(gè)觀察戳記?;蛘?,如果是樂觀讀,則在驗(yàn)證后返回它。該方法在所有其他情況下返回0,因此作為“tryUnlock”的形式可能很有用。
演示例子
下面用一個(gè)例子演示下StampedLock的使用,例子來源jdk中的javadoc。
測試用例:
結(jié)果:
性能對(duì)比
正是由于StampedLock?的樂觀讀模式,早就StampedLock的高性能和高吞吐量,那么具體的性能提高有多少呢?
下圖是和ReadWritLock相比,在一個(gè)線程情況下,讀速度是其4倍左右,寫是1倍。
下圖是16個(gè)線程情況下,讀性能是其幾十倍,寫性能也是近10倍左右:
下圖是吞吐量提高:
那么這樣是不是說StampedLock?可以全方位的替代ReentrantReadWriteLock?, 答案是否定的,StampedLock?相對(duì)于ReentrantReadWriteLock有下面兩個(gè)問題:
- 不支持條件變量Condition
- 不支持可重入
所以最終選擇StampedLock?還是ReentrantReadWriteLock,還是要看具體的業(yè)務(wù)場景。
總結(jié)
本文主要講解了StampedLock?的功能和使用,至于原理,StampedLock雖然不像其它鎖一樣定義了內(nèi)部類來實(shí)現(xiàn)AQS框架,但是StampedLock的基本實(shí)現(xiàn)思路還是利用CLH隊(duì)列進(jìn)行線程的管理,通過同步狀態(tài)值來表示鎖的狀態(tài)和類型,具體的源碼實(shí)現(xiàn)大家感興趣的自己可以追蹤看看。