StampedLock:一個并發編程中非常重要的票據鎖( 二 )


StampedLock實現思想StampedLock內部是基于CLH鎖實現的,CLH是一種自旋鎖,能夠保證沒有“饑餓現象”的發生,并且能夠保證FIFO(先進先出)的服務順序 。
在CLH中,鎖維護一個等待線程隊列,所有申請鎖 , 但是沒有成功的線程都會存入這個隊列中 , 每一個節點代表一個線程,保存一個標記位(locked),用于判斷當前線程是否已經釋放鎖,當locked標記位為true時 ,  表示獲取到鎖,當locked標記位為false時,表示成功釋放了鎖 。
當一個線程試圖獲得鎖時,取得等待隊列的尾部節點作為其前序節點,并使用類似如下代碼判斷前序節點是否已經成功釋放鎖:
while (pred.locked) { //省略操作}只要前序節點(pred)沒有釋放鎖,則表示當前線程還不能繼續執行 , 因此會自旋等待;反之,如果前序線程已經釋放鎖,則當前線程可以繼續執行 。
釋放鎖時,也遵循這個邏輯,線程會將自身節點的locked位置標記為false,后續等待的線程就能繼續執行了,也就是已經釋放了鎖 。
StampedLock的實現思想總體來說,還是比較簡單的 , 這里就不展開講了 。
StampedLock的注意事項在讀多寫少的高并發環境下,StampedLock的性能確實不錯,但是它不能夠完全取代ReadWriteLock 。在使用的時候,也需要特別注意以下幾個方面 。
StampedLock不支持重入沒錯 , StampedLock是不支持重入的 , 也就是說,在使用StampedLock時,不能嵌套使用,這點在使用時要特別注意 。
StampedLock不支持條件變量第二個需要注意的是就是StampedLock不支持條件變量,無論是讀鎖還是寫鎖,都不支持條件變量 。
StampedLock使用不當會導致CPU飆升這點也是最重要的一點,在使用時需要特別注意:如果某個線程阻塞在StampedLock的readLock()或者writeLock()方法上時,此時調用阻塞線程的interrupt()方法中斷線程 , 會導致CPU飆升到100% 。例如 , 下面的代碼所示 。
public void testStampedLock() throws Exception{ final StampedLock lock = new StampedLock(); Thread thread01 = new Thread(()->{ // 獲取寫鎖 lock.writeLock(); // 永遠阻塞在此處 , 不釋放寫鎖 LockSupport.park(); });thread01.start(); // 保證thread01獲取寫鎖 Thread.sleep(100); Thread thread02 = new Thread(()-> //阻塞在悲觀讀鎖 lock.readLock() );thread02.start(); // 保證T2阻塞在讀鎖 Thread.sleep(100); //中斷線程thread02 //會導致線程thread02所在CPU飆升thread02.interrupt();thread02.join();}運行上面的程序,會導致thread02線程所在的CPU飆升到100% 。
這里,有很多小伙伴不太明白為啥LockSupport.park();會導致thread01會永遠阻塞 。這里 , 冰河為你畫了一張線程的生命周期圖,如下所示 。

StampedLock:一個并發編程中非常重要的票據鎖

文章插圖
這下明白了吧?在線程的生命周期中,有幾個重要的狀態需要說明一下 。
  • NEW:初始狀態 , 線程被構建 , 但是還沒有調用start()方法 。
  • RUNNABLE:可運行狀態,可運行狀態可以包括:運行中狀態和就緒狀態 。
  • BLOCKED:阻塞狀態 , 處于這個狀態的線程需要等待其他線程釋放鎖或者等待進入synchronized 。
  • WAITING:表示等待狀態,處于該狀態的線程需要等待其他線程對其進行通知或中斷等操作,進而進入下一個狀態 。
  • TIME_WAITING:超時等待狀態 ??梢栽谝欢ǖ臅r間自行返回 。
  • TERMINATED:終止狀態,當前線程執行完畢 。
看完這個線程的生命周期圖,知道為啥調用LockSupport.park();會使thread02阻塞了吧?
所以 , 在使用StampedLock時 , 一定要注意避免線程所在的CPU飆升的問題 。那如何避免呢?
那就是使用StampedLock的readLock()方法或者讀鎖和使用writeLock()方法獲取寫鎖時,一定不要調用線程的中斷方法來中斷線程,如果不可避免的要中斷線程的話 , 一定要用StampedLock的readLockInterruptibly()方法獲取可中斷的讀鎖和使用StampedLock的writeLockInterruptibly()方法獲取可中斷的悲觀寫鎖 。
最后,對于StampedLock的使用,JDK官方給出的StampedLock示例本身就是一個最佳實踐了,小伙伴們可以多看看JDK官方給出的StampedLock示例,多多體會下StampedLock的使用方式和背后原理與核心思想 。
點擊關注 , 第一時間了解華為云新鮮技術~
【StampedLock:一個并發編程中非常重要的票據鎖】

推薦閱讀