1. 程式人生 > 其它 >讀多寫少的場景下,竟然還有比讀寫鎖更牛X的鎖?

讀多寫少的場景下,竟然還有比讀寫鎖更牛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);
 }