6.3 JUC — 阻塞同步機制AQS

AbstractQueuedSynchronizer(AQS),即隊列同步器。它是構建鎖或者其他同步組件的基礎框架(如ReentrantLock、ReentrantReadWriteLock、Semaphore等),它是JUC併發包中的核心基礎組件。AQS的主要使用方式是繼承,子類通過繼承同步器並實現它的抽象方法來管理同步狀態。

Node節點

AQS維護了一個靜態內部類Node,它包含了以下屬性:

  1. static final Node SHARED:一種標識,表示被標識的結點是共享式。
  2. static final Node EXCLUSIVE:一種標識,表示被標識的結點是獨佔式。
  3. volatile int waitStatus:當前結點的等待狀態,它有1,0,-1,-2,-3五個取值。
    1. static final int CANCELLED =  1;  被中斷或者獲取同步狀態超時會被置爲該狀態,且在該狀態下的線程不再被阻塞。
    2. 0爲初始化默認值。
    3. static final int SIGNAL    = -1;  線程如果釋放了或者取消了同步狀態,則會將對應的結點置爲該狀態,用於通知下一個節點,準備獲取同步狀態。
    4. static final int CONDITION = -2; 當前節點在Condition中的等待隊列上,其他線程調用了singal方法後,該節點會從等待隊列轉移到AQS的同步隊列上,等待獲取同步鎖。
    5. static final int PROPAGATE = -3; 與共享式獲取同步狀態有關,該狀態標識的結點對應線程處於可運行的狀態。
  4. volatile Node prev:前驅結點(給CLH同步隊列使用)
  5. volatile Node next:後繼結點(給CLH同步隊列使用)
  6. volatile Thread thread:當前節點對應的線程
  7. Node nextWaiter:當前節點在Condition中等待隊列上的下一個節點(給Condition等待隊列使用)

上述屬性中,注意其中a、b以及c中的狀態信息都是static類型的,也就是說它們只是一些標識信息,是所有節點共用的。而其他屬性則是每個節點自身的信息。

CLH同步隊列

CLH(Craig, Landin, and Hagersten locks) 同步隊列 是一個FIFO雙向隊列,其內部通過節點head和tail記錄隊首和隊尾元素,隊列元素的類型爲Node。AQS依賴它來完成同步狀態state的管理,CLH提供了一些重要的函數:

線程同步狀態相關

  • tryAcquire(int arg):獨佔式獲取同步狀態,獲取同步狀態成功後,其他線程需要等待該線程釋放同步狀態才能獲取同步狀態
  • tryRelease(int arg):獨佔式釋放同步狀態;
  • tryAcquireShared(int arg):共享式獲取同步狀態,返回值大於等於0則表示獲取成功,否則獲取失敗;
  • tryReleaseShared(int arg):共享式釋放同步狀態;
  • acquire(int arg):獨佔式獲取同步狀態,如果當前線程獲取同步狀態成功,則由該方法返回,否則,將會進入同步隊列等待,該方法將會調用可重寫的tryAcquire(int arg)方法;
  • acquireInterruptibly(int arg):與acquire(int arg)相同,但是該方法響應中斷,當前線程爲獲取到同步狀態而進入到同步隊列中,如果當前線程被中斷,則該方法會拋出InterruptedException異常並返回;
  • tryAcquireNanos(int arg,long nanos):超時獲取同步狀態,如果當前線程在nanos時間內沒有獲取到同步狀態,那麼將會返回false,已經獲取則返回true;
  • acquireShared(int arg):共享式獲取同步狀態,如果當前線程未獲取到同步狀態,將會進入同步隊列等待,與獨佔式的主要區別是在同一時刻可以有多個線程獲取到同步狀態;
  • acquireSharedInterruptibly(int arg):共享式獲取同步狀態,響應中斷;
  • tryAcquireSharedNanos(int arg, long nanosTimeout):共享式獲取同步狀態,增加超時限制;
  • release(int arg):獨佔式釋放同步狀態,該方法會在釋放同步狀態之後,將同步隊列中第一個節點包含的線程喚醒;
  • releaseShared(int arg):共享式釋放同步狀態;

CLH狀態相關

  • getState():返回同步狀態的當前值;
  • setState(int newState):設置當前同步狀態;
  • compareAndSetState(int expect, int update):使用CAS設置當前狀態,該方法能夠保證狀態設置的原子性;
  • CLH隊列入列就是tail指向新節點、新節點的prev指向當前最後的節點,當前最後一個節點的next指向當前節點。

同步狀態的獲取與釋放

在AQS中維護着一個FIFO(先進先出)的同步隊列CLH,當線程獲取同步狀態失敗後,則會加入到這個CLH同步隊列的對尾並一直保持着自旋。在CLH同步隊列中的線程在自旋時會判斷其前驅節點是否爲首節點,如果爲首節點則不斷嘗試獲取同步狀態,獲取成功則退出CLH同步隊列。當線程執行完邏輯後,會釋放同步狀態,釋放後會喚醒其後繼節點。

獲取同步狀態(入口:acquire相關方法)

釋放同步狀態(入口:release相關方法)

Condition等待隊列

在經典的生產者-消費者模式中,可以使用Object.wait()和Object.notify()阻塞和喚醒線程,但是只能有一個等待隊列。在AQS中也提供了類似的機制,但是可以更靈活的建立多個等待隊列。Condition隊列有以下特點:

  1. 提供了await()方法使一個線程陷入等待,並提供signal()方法來支持線程的喚醒。
  2. Condition和Wait/Notify通知機制類似,必須在Lock中使用,即在使用之前必須是已經獲取到鎖的狀態(Wait/Notify必須在synchronized中使用)。
  3. 執行signal()方法後,等待隊列的結點被轉移至CLH同步隊列中競爭鎖。

Condition等待隊列也使用了和CLH同步隊列一樣的結點,類似地使用firstWaiter與lastWaiter兩個指針指向隊列首尾,但是隻使用了node的nextWaiter屬性(而沒有用next和prev)使其變成了一個單向鏈表(也是FIFO)。Condition等待隊列提供了一些控制線程狀態的重要函數:

  1. 線程等待阻塞
    1. await():讓當前線程進入等待阻塞狀態,使其轉移到condition隊列中,直到其他線程喚醒或者中斷此線程。
    2. await(long time, TimeUnit unit):讓當前線程進入等待阻塞狀態,使其轉移到condition隊列中,直到其他線程喚醒、中斷、超時。
    3. awaitNanos(long nanosTimeout):納秒級別控制,讓當前線程進入等待阻塞狀態,使其轉移到condition隊列中,直到其他線程喚醒、中斷、超時,並返回剩餘時間。
    4. awaitUninterruptibly():讓當前線程進入等待阻塞狀態,且不會響應線程的中斷。
    5. awaitUntil(Date deadline):類似於第二種情況。
  2. 線程喚醒
    1. signal():喚醒一個等待在Condition上的線程,使其轉到同步隊列中
    2. signalAll():喚醒所有等待在Condition上的線程,使它們轉到同步隊列中

LockSupport

可以發現,在同步隊列和等待隊列中對線程的很多操作都與LockSupport有關。LockSupport對外提供了一些靜態方法用來操控當前線程:

  1. 線程阻塞相關
    1. park():阻塞當前線程,直到其他線程調用unpark(Thread)方法或者當前線程被中斷
    2. parkNanos(long):阻塞當前線程,直到其他線程調用unpark(Thread)方法、當前線程被中斷、超時(相對時間)
    3. parkUntil(long):阻塞當前線程,直到某個時間(絕對時間)
  2. 線程喚醒相關
    1. unpark(Thread):喚醒某個線程

而LockSupport的底層還是由Unsafe類來完成。

發佈了23 篇原創文章 · 獲贊 5 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章