讀寫鎖 StampedLock
1. 前言
本節(jié)帶領(lǐng)大家認識第二個常用的 Java 并發(fā)鎖工具之 StampedLock。
本節(jié)先簡單介紹 StampedLock 的基本概念,然后介紹關(guān)鍵的編程方法,最后通過一個編程例子為大家展示 StampedLock 工具類的用法。
下面我們正式開始介紹吧。
2. 概念解釋
我們先解釋一組概念:悲觀鎖、樂觀鎖。
悲觀鎖 指的是對數(shù)據(jù)修改持保守態(tài)度,在整個數(shù)據(jù)處理時,先將數(shù)據(jù)鎖定狀態(tài),然后再進行修改處理。之所以叫做悲觀鎖,是因為這是一種對數(shù)據(jù)的修改抱有悲觀態(tài)度的并發(fā)控制方式,認為數(shù)據(jù)被并發(fā)修改的概率比較大,所以需要在修改之前先加鎖。
樂觀鎖 正好相反,抱著數(shù)據(jù)訪問一般情況下不會造成沖突的觀點,先對數(shù)據(jù)做修改,在正式提交修改結(jié)果是才會做沖突檢查,如果發(fā)現(xiàn)修改的是舊版本的數(shù)據(jù),則返回修改失敗,否則提交修改。
StampedLock 是對 ReentrantReadWriteLock 的改進。相比 ReentrantReadWriteLock 采用了悲觀鎖的思想對數(shù)據(jù)修改的并發(fā)控制,StampedLock 使用了樂觀思想的加鎖實現(xiàn),具有更高的并發(fā)性。
下面我們學習其關(guān)鍵的編程方法。
3. StampedLock 的編程方法
StampedLock 提供了三種并發(fā)控制模式,介紹這三種模式過程中,我們穿插介紹關(guān)鍵的編程方法。
3.1. 獨占寫模式
功能和 ReentrantReadWriteLock 的寫鎖類似。獨占寫模式相關(guān)的幾個方法如下。
long stamp = writeLock () 方法:獲取獨占寫鎖,可能會被阻塞。如果獲取鎖成功則返回一個 stamp;
tryWriteLock () 方法:嘗試獲取獨占寫鎖,類似 writeLock () 方法,只是獲取不到時立刻返回不會阻塞;
tryWriteLock (long time, TimeUnit unit) 方法:允許在給定的時間內(nèi)嘗試獲取獨占寫鎖,超時仍然未獲取到時則返回;
writeLockInterruptibly () 方法:類似 writeLock () 但允許獲取鎖的過程被打斷;
unlockWrite (long stamp) 方法:用于釋放獨占寫鎖;
tryUnlockWrite () 方法:類似 unlockWrite (), 但允許不需要 stamp 郵戳參數(shù)。
3.2. 悲觀讀模式
功能和 ReentrantReadWriteLock 的讀鎖類似。悲觀讀模式相關(guān)的幾個方法如下。
long stamp = readLock () 方法:獲取獨占讀鎖,可能會被阻塞。如果獲取鎖成功則返回一個 stamp;
unlockRead (long stamp) 方法:用于釋放讀鎖;
tryReadLock () 方法:嘗試獲取讀鎖,類似 readLock () 方法,只是獲取不到時立刻返回不會阻塞;
tryReadLock (long time, TimeUnit unit) 方法:允許在給定的時間內(nèi)嘗試獲取讀鎖,超時仍然未獲取到時則返回;
readLockInterruptibly () 方法:類似 readLock () 但允許獲取鎖的過程被打斷。
3.3. 樂觀讀模式
這是一種優(yōu)化的讀模式。樂觀讀模式相關(guān)的幾個方法如下。
tryOptimisticRead () 方法:非阻塞嘗試樂觀獲取讀鎖,只有當寫鎖沒有被獲取時返回一個非 0 的 stamp 。樂觀讀取模式適用于短時間讀取操作,降低競爭和提高吞吐量。在使用時一般需將數(shù)據(jù)存儲到一個副本中,在后繼處理中用于對比數(shù)據(jù)是否是最新狀態(tài);
validate (long stamp) 方法:用于檢查在獲取到讀鎖 stamp 后,鎖有沒被其他寫線程搶占。如果寫鎖沒有被獲取,那么 validate () 方法返回 true??啥啻握{(diào)用驗證這一信息。
另外,此類也提供了一組讀寫鎖之間的轉(zhuǎn)換方法:
tryConvertToWriteLock (long stamp) 方法:嘗試轉(zhuǎn)換為寫鎖。轉(zhuǎn)換條件:
tryConvertToReadLock (long stamp) 方法:嘗試轉(zhuǎn)換為悲觀讀鎖。
tryConvertToOptimisticRead (long stamp) 方法:嘗試轉(zhuǎn)換為樂觀讀鎖。
注意此類的編程方法有這樣一個共通特征:
所有獲取鎖的方法,都返回一個郵戳(Stamp),Stamp 為 0 表示獲取失敗,其余都表示成功;
所有釋放鎖的方法,都需要一個郵戳(Stamp),這個 Stamp 必須是和成功獲取鎖時得到的 Stamp 一致;
下面我們舉一個具體的編程例子。
4. 編程示例
上面介紹了核心編程方法,我們給出一個非常簡潔明了的官方例子,切實體會一下 StampedLock 的用法。
import java.util.concurrent.locks.StampedLock;
public class StampedLockTest {
// 成員變量
private double x, y;
// 鎖實例
private final StampedLock sl = new StampedLock();
// 排它鎖-寫鎖(writeLock)
void move(double deltaX, double deltaY) {
long stamp = sl.writeLock();
try {
x += deltaX;
y += deltaY;
} finally {
sl.unlockWrite(stamp);
}
}
// 一個只讀方法
// 其中存在樂觀讀鎖到悲觀讀鎖的轉(zhuǎn)換
double distanceFromOrigin() {
// 嘗試獲取樂觀讀鎖
long stamp = sl.tryOptimisticRead();
// 將全部變量拷貝到方法體棧內(nèi)
double currentX = x, currentY = y;
// 檢查在獲取到讀鎖stamp后,鎖有沒被其他寫線程搶占
if (!sl.validate(stamp)) {
// 如果被搶占則獲取一個共享讀鎖(悲觀獲?。?/span>
stamp = sl.readLock();
try {
// 將全部變量拷貝到方法體棧內(nèi)
currentX = x;
currentY = y;
} finally {
// 釋放共享讀鎖
sl.unlockRead(stamp);
}
}
// 返回計算結(jié)果
return Math.sqrt(currentX * currentX + currentY * currentY);
}
// 獲取讀鎖,并嘗試轉(zhuǎn)換為寫鎖
void moveIfAtOrigin(double newX, double newY) {
long stamp = sl.tryOptimisticRead();
try {
// 如果當前點在原點則移動
while (x == 0.0 && y == 0.0) {
// 嘗試將獲取的讀鎖升級為寫鎖
long ws = sl.tryConvertToWriteLock(stamp);
// 升級成功,則更新stamp,并設置坐標值,然后退出循環(huán)
if (ws != 0L) {
stamp = ws;
x = newX;
y = newY;
break;
} else {
// 讀鎖升級寫鎖失敗則釋放讀鎖,顯示獲取獨占寫鎖,然后循環(huán)重試
sl.unlockRead(stamp);
stamp = sl.writeLock();
}
}
} finally {
sl.unlock(stamp);
}
}
}
注意在使用時,獲取鎖的操作應該放在 try 之前,而釋放鎖的操作需要放在 finally 中,可確保鎖釋放。另外需要注意 StampedLock 具有不可重入性。
5. 小結(jié)
本節(jié)解釋了 StampedLock 的基本概念和主要的編程方法,且通過一個簡單的例子展示了其用法,更多關(guān)于此工具類的概念和原理介紹,可閱讀 “Java 并發(fā)原理入門教程” 。希望大家在學習過程中,多思考勤練習,早日掌握之。