18 | StampedLock:有沒(méi)有比讀寫鎖更快的鎖?

在上一篇文章中,我們介紹了讀寫鎖,學(xué)習(xí)完之后你應(yīng)該已經(jīng)知道“讀寫鎖允許多個(gè)線程同時(shí)讀共享變量,適用于讀多寫少的場(chǎng)景”。那在讀多寫少的場(chǎng)景中,還有沒(méi)有更快的技術(shù)方案呢?還真有,Java 在 1.8 這個(gè)版本里,提供了一種叫 StampedLock 的鎖,它的性能就比讀寫鎖還要好。

下面我們就來(lái)介紹一下 StampedLock 的使用方法、內(nèi)部工作原理以及在使用過(guò)程中需要注意的事項(xiàng)。

StampedLock 支持的三種鎖模式

我們先來(lái)看看在使用上 StampedLock 和上一篇文章講的 ReadWriteLock 有哪些區(qū)別。

ReadWriteLock 支持兩種模式:一種是讀鎖,一種是寫鎖。而 StampedLock 支持三種模式,分別是:寫鎖、悲觀讀鎖和樂(lè)觀讀。其中,寫鎖、悲觀讀鎖的語(yǔ)義和 ReadWriteLock 的寫鎖、讀鎖的語(yǔ)義非常類似,允許多個(gè)線程同時(shí)獲取悲觀讀鎖,但是只允許一個(gè)線程獲取寫鎖,寫鎖和悲觀讀鎖是互斥的。不同的是:StampedLock 里的寫鎖和悲觀讀鎖加鎖成功之后,都會(huì)返回一個(gè) stamp;然后解鎖的時(shí)候,需要傳入這個(gè) stamp。相關(guān)的示例代碼如下。


final StampedLock sl = 
  new StampedLock();
  
// 獲取/釋放悲觀讀鎖示意代碼
long stamp = sl.readLock();
try {
  //省略業(yè)務(wù)相關(guān)代碼
} finally {
  sl.unlockRead(stamp);
}

// 獲取/釋放寫鎖示意代碼
long stamp = sl.writeLock();
try {
  //省略業(yè)務(wù)相關(guān)代碼
} finally {
  sl.unlockWrite(stamp);
}

StampedLock 的性能之所以比 ReadWriteLock 還要好,其關(guān)鍵是 StampedLock 支持樂(lè)觀讀的方式。ReadWriteLock 支持多個(gè)線程同時(shí)讀,但是當(dāng)多個(gè)線程同時(shí)讀的時(shí)候,所有的寫操作會(huì)被阻塞;而 StampedLock 提供的樂(lè)觀讀,是允許一個(gè)線程獲取寫鎖的,也就是說(shuō)不是所有的寫操作都被阻塞。

注意這里,我們用的是“樂(lè)觀讀”這個(gè)詞,而不是“樂(lè)觀讀鎖”,是要提醒你,樂(lè)觀讀這個(gè)操作是無(wú)鎖的,所以相比較 ReadWriteLock 的讀鎖,樂(lè)觀讀的性能更好一些。

進(jìn)一步理解樂(lè)觀讀

如果你曾經(jīng)用過(guò)數(shù)據(jù)庫(kù)的樂(lè)觀鎖,可能會(huì)發(fā)現(xiàn) StampedLock 的樂(lè)觀讀和數(shù)據(jù)庫(kù)的樂(lè)觀鎖有異曲同工之妙。的確是這樣的,就拿我個(gè)人來(lái)說(shuō),我是先接觸的數(shù)據(jù)庫(kù)里的樂(lè)觀鎖,然后才接觸的 StampedLock,我就覺(jué)得我前期數(shù)據(jù)庫(kù)里樂(lè)觀鎖的學(xué)習(xí)對(duì)于后面理解 StampedLock 的樂(lè)觀讀有很大幫助,所以這里有必要再介紹一下數(shù)據(jù)庫(kù)里的樂(lè)觀鎖。

還記得我第一次使用數(shù)據(jù)庫(kù)樂(lè)觀鎖的場(chǎng)景是這樣的:在 ERP 的生產(chǎn)模塊里,會(huì)有多個(gè)人通過(guò) ERP 系統(tǒng)提供的 UI 同時(shí)修改同一條生產(chǎn)訂單,那如何保證生產(chǎn)訂單數(shù)據(jù)是并發(fā)安全的呢?我采用的方案就是樂(lè)觀鎖。

樂(lè)觀鎖的實(shí)現(xiàn)很簡(jiǎn)單,在生產(chǎn)訂單的表 product_doc 里增加了一個(gè)數(shù)值型版本號(hào)字段 version,每次更新 product_doc 這個(gè)表的時(shí)候,都將 version 字段加 1。生產(chǎn)訂單的 UI 在展示的時(shí)候,需要查詢數(shù)據(jù)庫(kù),此時(shí)將這個(gè) version 字段和其他業(yè)務(wù)字段一起返回給生產(chǎn)訂單 UI。假設(shè)用戶查詢的生產(chǎn)訂單的 id=777,那么 SQL 語(yǔ)句類似下面這樣:


select id,... ,version
from product_doc
where id=777
    

用戶在生產(chǎn)訂單 UI 執(zhí)行保存操作的時(shí)候,后臺(tái)利用下面的 SQL 語(yǔ)句更新生產(chǎn)訂單,此處我們假設(shè)該條生產(chǎn)訂單的 version=9。


update product_doc 
set version=version+1,...
where id=777 and version=9
    

如果這條 SQL 語(yǔ)句執(zhí)行成功并且返回的條數(shù)等于 1,那么說(shuō)明從生產(chǎn)訂單 UI 執(zhí)行查詢操作到執(zhí)行保存操作期間,沒(méi)有其他人修改過(guò)這條數(shù)據(jù)。因?yàn)槿绻@期間其他人修改過(guò)這條數(shù)據(jù),那么版本號(hào)字段一定會(huì)大于 9。

你會(huì)發(fā)現(xiàn)數(shù)據(jù)庫(kù)里的樂(lè)觀鎖,查詢的時(shí)候需要把 version 字段查出來(lái),更新的時(shí)候要利用 version 字段做驗(yàn)證。這個(gè) version 字段就類似于 StampedLock 里面的 stamp。這樣對(duì)比著看,相信你會(huì)更容易理解 StampedLock 里樂(lè)觀讀的用法。

StampedLock 使用注意事項(xiàng)

對(duì)于讀多寫少的場(chǎng)景 StampedLock 性能很好,簡(jiǎn)單的應(yīng)用場(chǎng)景基本上可以替代 ReadWriteLock,但是StampedLock 的功能僅僅是 ReadWriteLock 的子集,在使用的時(shí)候,還是有幾個(gè)地方需要注意一下。

StampedLock 在命名上并沒(méi)有增加 Reentrant,想必你已經(jīng)猜測(cè)到 StampedLock 應(yīng)該是不可重入的。事實(shí)上,的確是這樣的,StampedLock 不支持重入。這個(gè)是在使用中必須要特別注意的。

另外,StampedLock 的悲觀讀鎖、寫鎖都不支持條件變量,這個(gè)也需要你注意。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容