Java高併發21-AQS在共享,獨佔場景下的源碼介紹

一、AQS--鎖的底層支持

1.AQS是什麼

  • AQS是AbstractQueuedSychronizer的簡稱,即抽象同步隊列的簡稱,這是實現同步器的重要組件,是一個抽象類,雖然在實際工作中很燒用到它,但是瞭解它的內部原理是很有必要的,並法包中鎖的底層就是使用該抽象類實現的,下面類圖 21.1

2.分析AQS類

  • AQS是一個雙向隊列,head和tail變量類型是Node類型,分別用於表示隊列的隊首和隊尾
  • 在AQS中維護一個單一的狀態信息state,可以通過getState,setState、compareAndSetState函數來修改其值。
  • 對於ReentrantLock的實現來說,state可以用來表示當前線程獲取鎖的可重入次數;對於讀寫鎖ReentrantReadWriteLock來說,state的高16位表示讀狀態,也就是獲取該讀鎖的次數,低16位表示獲取到寫鎖的程序可重入次數;對於semaphore來說,state用來表示當前可用信號的個數;對於CountDownlatch來說,state用來表示計算器當前的值。
  • 對於AQS來說,線程同步最關鍵的就是對state的操作,根據state是否屬於同一個線程,操作state的方式分爲獨佔式和共享方式
  • 在獨佔方式下,獲取和釋放線程的方法爲:void acquire(int arg) void acquireInterruptilbly(int arg) boolean release(int arg)
  • 在共享方式下,獲取和釋放線程的方法爲:void acquireShared(int arg) void acquireSharedInterruptibly(int arg) boolean releaseShared(int arg)
  • 使用獨佔式獲取資源是與具體線程綁定的,一個線程獲取到了資源,就會標記這個線程獲取到了,其他線程再嘗試操作state獲取資源時會發現當前資源不是自己持有的,就會在獲取失敗之後被阻塞。比如獨佔鎖ReentrantLock的實現,在AQS內部首先會使用CAS操作把state從0變成1,然後設置當前鎖的持有者是當前線程,當線程再次獲取鎖時發現鎖的持有者就是自己,則會把狀態值從1變成2,也就是設置可重入次數,而當另外一個線程獲取鎖的時候發現自己不是鎖的持有者,就會被放入AQS阻塞隊列之中。
  • 對應共享方式的資源是與具體線程不相關的,當多個線程使用CAS操作去競爭資源的時候,當一個線程獲取到了資源,另外一個資源只需要使用CAS操作獲取即可。例如:Semaphore信號量,當一個線程通過acquire獲取信號量的時候,會首先看當前信號量個數是否滿足需要,不滿足則把當前線程放入到阻塞隊列中,如果滿足則通過CAS操作獲取信號量,會首先看當前信號量個數是否滿足需要,不滿足則把當前線程放入阻塞隊列,如果滿足就會通過自旋CAS獲取信號量

3.分析Node內部類

  • 變量thread是一個Thread類型,用於存放進入AQS隊列的線程
  • 看一下幾個Node類型代表的含義
  • SHARED用來標記該線程是獲取共享資源的時候被阻塞掛起後放入AQS隊列的;
  • EXCLUSIVE用來標記線程是獲取獨佔資源時被掛起後放入AQS隊列的;
  • waitStatus是用來記錄線程等待狀態的,可以爲CANCELLED(線程被取消了),SIGNAL(線程需要被喚醒)、CONDITION(線程在條件隊列裏面等待)、PROPAGATE(釋放資源的時候需要通知其他節點)
  • prev和next分別代表當前節點的前驅節點和後置節點。

4.AQS內部ConditionObject

  • 這個類是用來實現線程同步的,ConditionObject可以直接訪問AQS對象內部的變量,比如state狀態值和AQS隊列,ConditonObject是條件變量,每個條件變量對應一個條件隊列(單向鏈表列隊),其用來存放調用條件變量的await方法後被阻塞的線程,如類圖所示,這個條件隊列的頭尾元素分別爲firstWaiter和lastWaiter。

5.獨佔方式下,獲取與釋放資源是如何及逆行的

  • 當一個線程調用acquire(int arg)獲取資源的時候,會首先使用tryAcquire嘗試獲取資源,具體就是設置state值,成功則直接返回,失敗則會當前線程封裝爲Node.EXCLUSIVE的Node節點,插入到AQS阻塞隊列的隊尾,並且調用LockSupport.park(this)方法掛起自己
 public final void acquire(int arg) {
  if(!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE),arg)) {
   selfInterrupt();
  }
 }
  • 當一個線程調用release(int arg)方法的時候,會嘗試使用tryRelease操作來釋放資源,也就是設置state的值,然後調用LockSupport.unpark(thread)方法激活在AQS隊列被阻塞的頭部的一個線程。被激活的線程,然後使用tryAcquire嘗試,看當前狀態值state值是否滿足自己的需要,如果滿足,則激活線程,繼續向下運行,否則還是會被放回AQS隊列中,然後被掛起
 public final boolean release(int arg) {
  if(tryRelease(arg)) {
   Node h = head;
   if(head != null && head.waitStatus != 0) {
    unparkSuccessor(h);
   }
   return true;
  }
  return false;
 }
  • AQS中的tryAcquire和tryRelease方法是沒有提供具體實現的,需要程序員自行在子類中進行定義,它們的實現就是使用CAS算法對state進行修改,成功返回true,失敗返回false,子類還需要定義當調用acquire和release方法時state狀態值的增減分別代表什麼含義。
  • 比如繼承自AQS中的獨佔鎖ReentrantLock,定義當status爲0的時候表示鎖空閒,1表示該鎖正在佔用,重寫tryAcquire方法,就是使用CAS算法,查看state是否爲0,如果爲0,那麼置爲1,並且返回true,否則,返回false;獨佔鎖在實現release的時候,在內部使用CAS算法把當前state的值從1修改爲0,並且設置當前線程的持有者爲null,然後返回true,如果CAS失敗,那麼返回false

6.共享方式下,獲取與釋放資源是如何及逆行的

  • 描述基本和獨佔鎖的方式一樣
  • 當一個線程調用acquireShared(int arg)獲取資源的時候,會首先使用tryAcquireShared嘗試獲取資源,具體就是設置state值,成功則直接返回,失敗則會當前線程封裝爲Node.SHARED的Node節點,插入到AQS阻塞隊列的隊尾,並且調用LockSupport.park(this)方法掛起自己
 public final void acquireShared(int arg) {
  if(tryAcquireShared(arg) < 0) {
   doAcquireShared(arg);
  }
 }
  • 當一個線程調用releaseShared(int arg)方法的時候,會嘗試使用tryRelease操作來釋放資源,也就是設置state的值,然後調用LockSupport.unpark(thread)方法激活在AQS隊列被阻塞的頭部的一個線程。被激活的線程,然後使用tryAcquire嘗試,看當前狀態值state值是否滿足自己的需要,如果滿足,則激活線程,繼續向下運行,否則還是會被放回AQS隊列中,然後被掛起
 public final boolean releaseShared(int arg) {
  if(tryReleaseShared(arg)) {
   doReleaseShared();
   return true;
  }
  return false;
 }
  • AQS類並沒有提供可用的tryAcquireShared和tryReleaseShared方法,正如AQS是鎖阻塞和同步器的基礎框架一樣,tryAcquireShared和tryReleaseShared需要由具體的子類進行實現,子類在實現tryAcquiredShared和tryReleaseShared時要根據具體場景使用CAS算法嘗試修改state狀態變量,成功則返回true,否則返回false
  • 比如繼承自AQS實現的讀寫鎖ReentrantReadWriteLock裏面的讀鎖在重寫tryAcquireShared時,首先查看寫鎖是否被其他線程持有,如果是則直接返回false,否則使用CAS遞增的state的高16位(在ReentrantReadWriteLock中,state的高16位爲獲取讀鎖的次數)
  • 比如繼承自AQS實現的讀寫鎖ReentrantReadWriteLock裏面的讀鎖在重寫tryReleaseShared時,在內部需要使用CAS算法把當前state的值高16位減1,然後返回true,如果CAS失敗那麼返回false
  • 基於AQS實現的鎖除了需要重寫上述這些方法之外,還需要重寫isHeldExclusively方法,來判斷鎖是被當前線程佔用還是被共享

7.另外對與獨佔方式下void acquire(int arg)和void acquireInterruptibly(int arg),與共享方式下void acquireShared(int arg)和void acquireSharedInterruptibly(int arg)之間有一個單詞Interruptibly的區別是什麼

  • 不帶interruptibly的方法意思就是不對中斷進行響應,比如線程在調用了不帶Interruptibly的方法獲取資源或者獲取資源失敗被掛起的時候,其他線程中斷了該線程,那麼該線程不會因爲被中斷而拋出異常,它還是繼續獲取資源或者被掛起,也就是不對中斷進行響應,忽略中斷
  • 帶有該單詞的方法要對中斷進行響應,也就是線程在調用了帶有該單詞的方法獲取資源獲取獲取資源失敗被掛起的時候,其他線程中斷了該線程,該線程就會拋出InterruptException異常而返回

二、源碼:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章