1. 程式人生 > 實用技巧 >AbstractQueuedSynchronizer(AQS) 總結篇

AbstractQueuedSynchronizer(AQS) 總結篇

簡介

在之前已經有6篇關於AQS原始碼分析的文章了,關於原始碼分析的一些問題可以去看看我之前的文章,文章連線可以在文末檢視。這一篇文章主要是對AQS的一些總結,或者說是面經。

AQS是什麼

AQS 全稱是AbstractQueuedSynchronizer,在java.util.concurrent.locks包下面,是一個抽象的可以實現阻塞執行緒、排隊控制、喚醒執行緒等操作的同步器基礎框架類,AQS 可以實現排它鎖、共享鎖、條件鎖、計數器等相關功能。

父類AbstractOwnableSynchronizer

AQS 繼承的父類AbstractOwnableSynchronizer,該類僅一個屬性用於記錄當前持有鎖的執行緒,提供get/set方法。

變數:同步狀態state

state 欄位是一個非常重要的欄位,可以基於state欄位的值定義出不同的同步鎖功能,比如:

  1. 基於state 的值實現排他鎖
    state 值為1代表鎖被佔用,值為0時代表鎖未被佔用。
    代表類:ReentrantLock
  2. 基於state的值實現讀寫鎖
    state 被分成兩部分,高16位記錄讀鎖次數,低16位記錄寫鎖次數
    代表類:ReentrantReadWriteLock
  3. 基於state的值實現限制執行緒數
    初始化一個state值,表示最大限制數,即可以做到允許最多N個執行緒同時執行,達到限流效果
    代表類:Semaphore
  4. 基於state的值實現倒計數
    初始化一個state值,state值為0時觸發喚醒動作
    代表類:CountDownLatch

兩個佇列

AQS 裡面有兩個佇列,我稱為同步佇列和條件佇列。條件佇列主要是實現條件鎖時用到的佇列,同步佇列就是維護喚醒執行緒的佇列。

  1. 同步佇列
    主要用於維護獲取互斥鎖失敗時入隊的執行緒
  2. 條件佇列
    呼叫await()的時候會釋放鎖,然後執行緒會加入到條件佇列,呼叫signal()喚醒的時候會把條件佇列中的執行緒節點移動到同步佇列中,等待再次獲得鎖

可以重寫的API

AQS 提供了 5 個可以自定義實現功能的API方法,基於這些方法,則可以實現不同型別的鎖功能。

  1. protected boolean tryAcquire(int arg)
    嘗試一次獲得一個排它鎖
  2. protected boolean tryRelease(int arg)
    嘗試一次釋放一個排它鎖
  3. protected int tryAcquireShared(int arg)
    嘗試一次獲得一個共享鎖
  4. protected boolean tryReleaseShared(int arg)
    嘗試一次釋放一個共享鎖
  5. protected boolean isHeldExclusively()
    驗證排它鎖是否被佔用

提供的模版方法

下面的這些模版方法,都用到了上面可以重寫的API方法。

  • 基於tryAcquireAPI提供的模版方法

    1. 獲得一個排它鎖,直到成功獲得鎖

      public final void acquire(int arg) {
          if (!tryAcquire(arg) &&
              acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
              selfInterrupt();
      }
      
    2. 獲得一個排它鎖,可被中斷

      public final void acquireInterruptibly(int arg)
                  throws InterruptedException {
          if (Thread.interrupted())
              throw new InterruptedException();
          if (!tryAcquire(arg))
              doAcquireInterruptibly(arg);
      }
      
    3. 獲得一個排它鎖,可超時或中斷

      public final boolean tryAcquireNanos(int arg, long nanosTimeout)
                  throws InterruptedException {
          if (Thread.interrupted())
              throw new InterruptedException();
          return tryAcquire(arg) ||
              doAcquireNanos(arg, nanosTimeout);
      }
      

    其中tryAcquire(arg)方法是需要自己實現的方法

  • 基於tryAcquireShared API提供的模版方法

    1. 獲得一個共享鎖,直到成功獲得鎖

      public final void acquireShared(int arg) {
          if (tryAcquireShared(arg) < 0)
              doAcquireShared(arg);
      }
      

      其中tryAcquireShared(arg)方法是需要自己實現的方法

    2. 獲得一個共享鎖,可被中斷

      public final void acquireSharedInterruptibly(int arg)
                  throws InterruptedException {
          if (Thread.interrupted())
              throw new InterruptedException();
          if (tryAcquireShared(arg) < 0)
              doAcquireSharedInterruptibly(arg);
      }
      
    3. 獲得一個共享鎖,支援超時或中斷

      public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
                  throws InterruptedException {
          if (Thread.interrupted())
              throw new InterruptedException();
          return tryAcquireShared(arg) >= 0 ||
              doAcquireSharedNanos(arg, nanosTimeout);
      }
      
  • 釋放一個排它鎖

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    

    其中tryRelease(arg)方法是需要自己實現的方法

  • 釋放一個共享鎖

    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
    

    其中tryReleaseShared(arg)方法是需要自己實現的方法

節點狀態

AQS 定義了5個佇列中節點狀態:

  1. 值為0,初始化狀態,表示當前節點在sync佇列中,等待著獲取鎖。
  2. CANCELLED,值為1,表示當前的執行緒被取消;
  3. SIGNAL,值為-1,表示當前節點的後繼節點包含的執行緒需要執行,也就是unpark;
  4. CONDITION,值為-2,表示當前節點在等待condition,也就是在condition佇列中;
  5. PROPAGATE,值為-3,表示當前場景下後續的acquireShared能夠得以執行;

其他

還有一個與AQS非常相似的類——AbstractQueuedLongSynchronizer,從命名上來看,多了一個Long,從原始碼上來看,他們兩個有完全相同的結構、屬性和方法,唯一不同之處就在於所有與狀態相關的引數和結果都定於為long型別,而不是int型別,當需要建立64位狀態的同步器(例如多級鎖和屏障)時,AbstractQueuedLongSynchronizer類可能很有用。

AQS實現原始碼分析

  1. 原始碼分析:同步基礎框架之AbstractQueuedSynchronizer(AQS)
  2. 原始碼分析:①ReentrantLock之公平鎖和非公平鎖
  3. 原始碼分析:②ReentrantLock之條件鎖Condition
  4. 原始碼分析:ReentrantReadWriteLock之讀寫鎖
  5. 原始碼分析:Semaphore之訊號量
  6. 原始碼分析:CountDownLatch 之倒計時門栓