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

1、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異常而返回

2、源碼:

相關文章
相關標籤/搜索