在上一篇文章中,我們介紹了讀寫鎖,學(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è)也需要你注意。