讀多寫少的場景下,竟然還有比讀寫鎖更牛X的鎖?
1)上一篇文章我們聊了讀寫鎖,他的適用場景是讀多寫少的場景下,那有沒有其它效能比讀寫鎖還要牛逼的鎖呢?
-
StampedLock ,java1.8誕生的。
2)StampedLock比讀寫鎖牛在什麼地方?
-
讀寫鎖分為兩種:讀鎖和寫鎖
-
StampedLock有三種模式:寫鎖和悲觀讀鎖,這兩個對應我們的讀寫鎖的寫鎖和讀鎖,功能是一樣的。但是他的殺手鐗是樂觀讀,注意是樂觀讀,不是樂觀讀鎖。
-
樂觀讀是一種操作,不涉及到鎖。當多個執行緒在讀的時候,允許一個執行緒獲取讀鎖,這個就是StampedLock與讀寫鎖不同的地方。因為不涉及到鎖,所以為了保障併發安全,會有一個stamp來作為安全的標誌。類似於我們資料庫樂觀鎖的version。
3)寫鎖和悲觀讀鎖與我們讀寫鎖的細緻區別是什麼?
-
他兩加鎖的時候會返回一個stamp,然後要解鎖的話,需要帶著這個stamp來。
final StampedLock sl =
new StampedLock();
// 獲取/釋放悲觀讀鎖示意程式碼
long stamp = sl.readLock();
try {
//省略業務相關程式碼
} finally {
sl.unlockRead(stamp);
}
// 獲取/釋放寫鎖示意程式碼
long stamp = sl.writeLock();
try {
//省略業務相關程式碼
} finally {
sl.unlockWrite(stamp);
}
4)樂觀讀是怎樣使用的?
-
tryOptimisticRead()就是樂觀讀,因為是無鎖的,所所以共享變數 x 和 y 讀入方法區域性變數時,x 和 y 有可能被其他執行緒修改了。因此最後讀完之後,還需要再次驗證一下是否存在寫操作,這個驗證操作是通過呼叫 validate(stamp) 來實現的。
class Point {
private int x, y;
final StampedLock sl =
new StampedLock();
//計算到原點的距離
int distanceFromOrigin() {
// 樂觀讀
long stamp =
sl.tryOptimisticRead();
// 讀入區域性變數,
// 讀的過程資料可能被修改
int curX = x, curY = y;
//判斷執行讀操作期間,
//是否存在寫操作,如果存在,
//則sl.validate返回false
if (!sl.validate(stamp)){
// 升級為悲觀讀鎖
stamp = sl.readLock();
try {
curX = x;
curY = y;
} finally {
//釋放悲觀讀鎖
sl.unlockRead(stamp);
}
}
return Math.sqrt(
curX * curX + curY * curY);
}
}
-
上面的程式碼中,如果樂觀讀的時候,存在寫操作,那麼就把它升級為悲觀讀鎖。這樣就避免了樂觀讀一直迴圈浪費大量的cpu,使用的時候儘量這樣去做。
5)StampedLock有哪些注意事項?
-
千萬不要線上程阻塞在 StampedLock 的 readLock() 或者 writeLock() 上時呼叫該阻塞執行緒的interrupt()方法,會導致執行這個執行緒的cpu掛掉的。如果實在要用中斷方法,那就用帶interrupt的悲觀讀鎖 readLockInterruptibly() 和寫鎖 writeLockInterruptibly()。
final StampedLock lock
= new StampedLock();
Thread T1 = new Thread(()->{
// 獲取寫鎖
lock.writeLock();
// 永遠阻塞在此處,不釋放寫鎖
LockSupport.park();
});
T1.start();
// 保證T1獲取寫鎖
Thread.sleep(100);
Thread T2 = new Thread(()->
//阻塞在悲觀讀鎖
lock.readLock()
);
T2.start();
// 保證T2阻塞在讀鎖
Thread.sleep(100);
//中斷執行緒T2
//會導致執行緒T2所在CPU飆升
T2.interrupt();
T2.join(); -
StampedLock的功能是不如讀寫鎖的那麼多的
-
StampedLock是不支援巢狀使用的,也就是可重入鎖。
6)以後有用到StampedLock的需求的時候,使用的模板應該是怎樣的?
StampedLock 讀模板:
final StampedLock sl =
new StampedLock();
// 樂觀讀
long stamp =
sl.tryOptimisticRead();
// 讀入方法區域性變數
......
// 校驗stamp
if (!sl.validate(stamp)){
// 升級為悲觀讀鎖
stamp = sl.readLock();
try {
// 讀入方法區域性變數
.....
} finally {
//釋放悲觀讀鎖
sl.unlockRead(stamp);
}
}
//使用方法區域性變數執行業務操作
......
StampedLock 寫模板:
long stamp = sl.writeLock();
try {
// 寫共享變數
......
} finally {
sl.unlockWrite(stamp);
}