StampedLock介紹
StampedLock是爲了優化可重入讀寫鎖性能的一個鎖實現工具,jdk8開始引入
相比於普通的ReentranReadWriteLock主要多了一種樂觀讀的功能
在API上增加了stamp的入參和返回值
不支持重入
StampedLock如何使用和使用價值
我看了上面的介紹仍然對StampedLock一頭霧水,下面我們來揭開StampedLock神祕的面紗
1、對於悲觀讀和悲觀寫的方法與ReentranReadWriteLock讀寫鎖效果一樣
下面是StampedLock的悲觀讀、寫鎖的實現
static ExecutorService service = Executors.newFixedThreadPool(10); static StampedLock lock = new StampedLock(); static long milli = 5000; static int count = 0;
private static long writeLock() {
long stamp = lock.writeLock(); //獲取排他寫鎖
count+=1;
lock.unlockWrite(stamp); //釋放寫鎖
System.out.println("數據寫入完成");
return System.currentTimeMillis();
}
private static void readLock() {//悲觀讀鎖 service.submit(() -> { int currentCount = 0; long stamp = lock.readLock(); //獲取悲觀讀鎖 try { currentCount = count; //獲取變量值 try { TimeUnit.MILLISECONDS.sleep(milli);//模擬讀取需要花費20秒 } catch (InterruptedException e) { e.printStackTrace(); } } finally { lock.unlockRead(stamp); //釋放讀鎖 } System.out.println("readLock==" + currentCount); //顯示最新的變量值 }); try { TimeUnit.MILLISECONDS.sleep(500);//要等一等讀鎖先獲得 } catch (InterruptedException e) { e.printStackTrace(); } }
測試一下效果:
public static void main(String[] args) { long l1 = System.currentTimeMillis(); readLock(); long l2 = writeLock(); System.out.println(l2-l1); }
因爲我對悲觀讀操作進行了5秒的數據讀取延遲,所以寫操作要等5秒後讀鎖釋放才能寫入數據
輸出結果:
數據寫入完成的時間比獲取讀鎖晚5043ms
讀到的數據仍然是寫入前的0
2、對於樂觀讀(如果沒有進入寫模式)可以減少一次讀鎖的性能消耗,並且不會阻塞寫入的操作
我們添加了一個樂觀讀的方法,這個方法仍然模擬讀取延遲5秒,樂觀讀實現需要參考下面的編程模式(獲取樂觀鎖、校驗是否進入寫模式)
private static void optimisticRead() { service.submit(() -> { long stamp = lock.tryOptimisticRead(); //嘗試獲取樂觀讀鎖 int currentCount = count; //獲取變量值 if (!lock.validate(stamp)) { //判斷count是否進入寫模式 stamp = lock.readLock(); //已經進入寫模式,沒辦法只能老老實實的獲取讀鎖 try { currentCount = count; //成功獲取到讀鎖,並重新獲取最新的變量值 } finally { lock.unlockRead(stamp); //釋放讀鎖 } } try { TimeUnit.MILLISECONDS.sleep(milli);//模擬讀取需要花費20秒 } catch (InterruptedException e) { e.printStackTrace(); } //走到這裏,說明count還沒有被寫,那麼可以不用加讀鎖,減少了讀鎖的開銷 System.out.println("optimisticRead==" + currentCount); //顯示最新的變量值 }); try { TimeUnit.MILLISECONDS.sleep(500);//要等一等讀鎖先獲得 } catch (InterruptedException e) { e.printStackTrace(); } }
測試一下效果:
public static void main(String[] args) { long l1 = System.currentTimeMillis(); optimisticRead(); long l2 = writeLock(); System.out.println(l2-l1); }
直接看一下輸出結果:
數據寫入完成的時間比獲取讀鎖晚543ms(說明樂觀讀並沒有阻塞寫操作)
5秒後讀到的數據仍然是寫入前的0
總結
可以看到相比直接用悲觀讀鎖,樂觀讀鎖可以:
1、進入悲觀讀鎖前先看下有沒有進入寫模式(說白了就是有沒有已經獲取了悲觀寫鎖)
2、如果其他線程已經獲取了悲觀寫鎖,那麼就只能老老實實的獲取悲觀讀鎖(這種情況相當於退化成了讀寫鎖)
3、如果其他線程沒有獲取悲觀寫鎖,那麼就不用獲取悲觀讀鎖了,減少了一次獲取悲觀讀鎖的消耗和避免了因爲讀鎖導致寫鎖阻塞的問題,直接返回讀的數據即可(必須再tryOptimisticRead和validate之間獲取好數據,否則數據可能會不一致了,試想如果過了validate再獲取數據,這時數據可能被修改並且讀操作也沒有任何保護措施)