天天看點

Java多線程 - AQS詳解

1. AQS産生背景

幾乎任一同步器都可以用來實作其他形式的同步器。例如,可以用可重入鎖實作信号量或者用信号量實作可重入鎖。但是,這樣做帶來的複雜性、開銷及不靈活使j.u.c最多隻能是一個二流工程,且缺乏吸引力。如果任何這樣的構造方式不能在本質上比其他形式更簡潔,那麼開發者就不應該随意地選擇其中的某個來建構另一個同步器。是以,JSR166基于AQS類建立了一個小架構,這個架構為構造同步器提供一種通用的機制,并且被j.u.c包中大部分類使用,同時很多使用者也可以用它來定義自己的同步器。這個就是j.u.c的作者Doug Lea大神的初衷,通過提供AQS這個基礎元件來建構j.u.c的各種工具類,至此就可以了解AQS的産生背景了。

2、AQS的設計和結構

  2.1 設計思想

同步器的核心方法是acquire和release操作

acquire操作是這樣的:

for (;;) {
    // 如果目前線程不在隊列中,則将其插入隊列
    final Node p = node.predecessor();
    if (p == head && tryAcquire(arg)) {
        setHead(node);
        p.next = null; // help GC
        failed = false;
        return interrupted;
    }
    // 擷取鎖失敗,阻塞目前線程
    if (shouldParkAfterFailedAcquire(p, node) &&
        parkAndCheckInterrupt())
        interrupted = true;
}       
           

release操作是這樣的:

public final boolean release(int arg) {
    if (tryRelease(arg)) {
    Node h = head;
    // 新的狀态允許某個被阻塞的線程擷取成功
    if (h != null && h.waitStatus != 0)
        // 解除隊列中一個或多個線程的阻塞狀态
        unparkSuccessor(h);
        return true;
    }
    return false;
}
           

從這兩個操作中的思想中我們可以提取出三大關鍵操作:同步器的狀态變更、線程阻塞和釋放、插入和移出隊列。是以為了實作這兩個操作,需要協調三大關鍵操作引申出來的三個基本元件:

·同步器狀态的原子性管理;

  ·線程阻塞與解除阻塞;

  ·隊列的管理;

同步狀态

AQS類使用單個int(32位)來儲存同步狀态,并暴露出getState、setState以及compareAndSet操作來讀取和更新這個同步狀态。其中屬性state被聲明為volatile,并且通過使用CAS指令來實作compareAndSetState,使得當且僅當同步狀态擁有一個一緻的期望值的時候,才會被原子地設定成新值,這樣就達到了同步狀态的原子性管理,確定了同步狀态的原子性、可見性和有序性。

 基于AQS的具體實作類(如鎖、信号量等)必須根據暴露出的狀态相關的方法定義tryAcquire和tryRelease方法,以控制acquire和release操作。當同步狀态滿足時,tryAcquire方法必須傳回true,而當新的同步狀态允許後續acquire時,tryRelease方法也必須傳回true。這些方法都接受一個int類型的參數用于傳遞想要的狀态。

 阻塞

直到JSR166,阻塞線程和解除線程阻塞都是基于Java的内置管程,沒有其它非基于Java内置管程的API可以用來達到阻塞線程和解除線程阻塞。唯一可以選擇的是Thread.suspend和Thread.resume,但是它們都有無法解決的競态問題,是以也沒法用,目前該方法基本已被抛棄。具體不能用的原因可以官方給出的答複。

  j.u.c.locks包提供了LockSupport類來解決這個問題。方法LockSupport.park阻塞目前線程直到有個LockSupport.unpark方法被調用。unpark的調用是沒有被計數的,是以在一個park調用前多次調用unpark方法隻會解除一個park操作。另外,它們作用于每個線程而不是每個同步器。一個線程在一個新的同步器上調用park操作可能會立即傳回,因為在此之前可以有多餘的unpark操作。但是,在缺少一個unpark操作時,下一次調用park就會阻塞。雖然可以顯式地取消多餘的unpark調用,但并不值得這樣做。在需要的時候多次調用park會更高效。park方法同樣支援可選的相對或絕對的逾時設定,以及與JVM的Thread.interrupt結合 ,可通過中斷來unpark一個線程。

隊列

整個架構的核心就是如何管理線程阻塞隊列,該隊列是嚴格的FIFO隊列,是以不支援線程優先級的同步。同步隊列的最佳選擇是自身沒有使用底層鎖來構造的非阻塞資料結構,業界主要有兩種選擇,一種是MCS鎖,另一種是CLH鎖。其中CLH一般用于自旋,但是相比MCS,CLH更容易實作取消和逾時,是以同步隊列選擇了CLH作為實作的基礎。

  CLH隊列實際并不那麼像隊列,它的出隊和入隊與實際的業務使用場景密切相關。它是一個連結清單隊列,通過AQS的兩個字段head(頭節點)和tail(尾節點)來存取,這兩個字段是volatile類型,初始化的時候都指向了一個空節點。如下圖:

Java多線程 - AQS詳解

入隊操作:CLH隊列是FIFO隊列,故新的節點到來的時候,是要插入到目前隊列的尾節點之後。試想一下,當一個線程成功地擷取了同步狀态,其他線程将無法擷取到同步狀态,轉而被構造成為節點并加入到同步隊列中,而這個加入隊列的過程必須要保證線程安全,是以同步器提供了一個CAS方法,它需要傳遞目前線程“認為”的尾節點和目前節點,隻有設定成功後,目前節點才正式與之前的尾節點建立關聯。入隊操作示意圖大緻如下:

Java多線程 - AQS詳解

出隊操作:因為遵循FIFO規則,是以能成功擷取到AQS同步狀态的必定是首節點,首節點的線程在釋放同步狀态時,會喚醒後續節點,而後續節點會在擷取AQS同步狀态成功的時候将自己設定為首節點。設定首節點是由擷取同步成功的線程來完成的,由于隻能有一個線程可以擷取到同步狀态,是以設定首節點的方法不需要像入隊這樣的CAS操作,隻需要将首節點設定為原首節點的後續節點同時斷開原節點、後續節點的引用即可。出隊操作示意圖大緻如下:

Java多線程 - AQS詳解

這一小節隻是簡單的描述了隊列的大概,目的是為了表達清楚隊列的設計架構,實際上CLH隊列已經和初始的CLH隊列已經發生了一些變化,具體的可以看檢視資料中Doug Lea的那篇論文中的3.3 Queues。

條件隊列

上一節的隊列其實是AQS的同步隊列,這一節的隊列是條件隊列,隊列的管理除了有同步隊列,還有條件隊列。AQS隻有一個同步隊列,但是可以有多個條件隊列。AQS架構提供了一個ConditionObject類,給維護獨占同步的類以及實作Lock接口的類使用。

  ConditionObject類實作了Condition接口,Condition接口提供了類似Object管程式的方法,如await、signal和signalAll操作,還擴充了帶有逾時、檢測和監控的方法。ConditionObject類有效地将條件與其它同步操作結合到了一起。該類隻支援Java風格的管程通路規則,這些規則中,當且僅當目前線程持有鎖且要操作的條件(condition)屬于該鎖時,條件操作才是合法的。這樣,一個ConditionObject關聯到一個ReentrantLock上就表現的跟内置的管程(通過Object.wait等)一樣了。兩者的不同僅僅在于方法的名稱、額外的功能以及使用者可以為每個鎖聲明多個條件。

ConditionObject類和AQS共用了内部節點,有自己單獨的條件隊列。signal操作是通過将節點從條件隊列轉移到同步隊列中來實作的,沒有必要在需要喚醒的線程重新擷取到鎖之前将其喚醒。signal操作大緻示意圖如下:

Java多線程 - AQS詳解
Java多線程 - AQS詳解

實作這些操作主要複雜在,因逾時或Thread.interrupt導緻取消了條件等待時,該如何處理。await和signal幾乎同時發生就會有競态問題,最終的結果遵照内置管程相關的規範。JSR133修訂以後,就要求如果中斷發生在signal操作之前,await方法必須在重新擷取到鎖後,抛出InterruptedException。但是,如果中斷發生在signal後,await必須傳回且不抛異常,同時設定線程的中斷狀态。

方法結構

如果我們了解了上一節的設計思路,我們大緻就能知道AQS的主要資料結構了。

元件 資料結構
同步狀态 volatile int state
阻塞 LockSupport類
隊列 Node節點
條件隊列 ConditionObject

進而再來看下AQS的主要方法及其作用。

屬性、方法 描述、作用
int getState() 擷取目前同步狀态
void setState(int newState) 設定目前同步狀态
boolean compareAndSetState(int expect, int update) 通過CAS設定目前狀态,此方法保證狀态設定的原子性
boolean tryAcquire(int arg) 鈎子方法,獨占式擷取同步狀态,AQS沒有具體實作,具體實作都在子類中,實作此方法需要查詢目前同步狀态并判斷同步狀态是否符合預期,然後再CAS設定同步狀态
boolean tryRelease(int arg) 鈎子方法,獨占式釋放同步狀态,AQS沒有具體實作,具體實作都在子類中,等待擷取同步狀态的線程将有機會擷取同步狀态
int tryAcquireShared(int arg) 鈎子方法,共享式擷取同步狀态,AQS沒有具體實作,具體實作都在子類中,傳回大于等于0的值表示擷取成功,反之失敗
boolean tryReleaseShared(int arg) 鈎子方法,共享式釋放同步狀态,AQS沒有具體實作,具體實作都在子類中
boolean isHeldExclusively() 鈎子方法,AQS沒有具體實作,具體實作都在子類中,目前同步器是否在獨占模式下被線程占用,一般該方法表示是否被目前線程所獨占
void acquire(int arg) 模闆方法,獨占式擷取同步狀态,如果目前線程擷取同步狀态成功,則由該方法傳回,否則會進入同步隊列等待,此方法會調用子類重寫的tryAcquire方法
void acquireInterruptibly(int arg) 模闆方法,與acquire相同,但是此方法可以響應中斷,目前線程未擷取到同步狀态而進入同步隊列中,如果目前線程被中斷,此方法會抛出InterruptedException并傳回
boolean tryAcquireNanos(int arg, long nanosTimeout) 模闆方法,在acquireInterruptibly基礎上增加了逾時限制,如果目前線程在逾時時間内沒有擷取到同步狀态,則會傳回false,如果擷取到了則會傳回true
boolean release(int arg) 模闆方法,獨占式的釋放同步狀态,該方法會在釋放同步狀态後,将同步隊列中的第一個節點包含的線程喚醒
void acquireShared(int arg) 模闆方法,共享式的擷取同步狀态,如果目前系統未擷取到同步狀态,将會進入同步隊列等待,與acquire的主要差別在于同一時刻可以有多個線程擷取到同步狀态
void acquireSharedInterruptibly(int arg) 模闆方法,與acquireShared一緻,但是可以響應中斷
boolean tryAcquireSharedNanos(int arg, long nanosTimeout) 模闆方法,在acquireSharedInterruptibly基礎上增加了逾時限制
boolean releaseShared(int arg) 模闆方法,共享式的釋放同步狀态
Collection<Thread> getQueuedThreads() 模闆方法,擷取等待在同步隊列上的線程集合
Node int waitStatus

等待狀态

1、 CANCELLED,值為1,在同步隊列中等待的線程等待逾時或者被中斷,需要從同步隊列中取消等待,節點進入該狀态後将不會變化;

2、 SIGNAL,值為-1,後續節點的線程處于等待狀态,而目前節點的線程如果釋放了同步狀态或者被取消,将會通知後續節點,使後續節點的線程得以運作;

3、 CONDITION,值為-2,節點在條件隊列中,節點線程等待在Condition上,當其他線程對Condition調用了signal()方法後,該節點将會從條件隊列中轉移到同步隊列中,加入到對同步狀态的擷取中;

4、 PROPAGATE,值為-3,表示下一次共享式同步狀态擷取将會無條件地傳播下去

Node prev 前驅節點,當節點加入同步隊列時被設定
Node next 後續節點
Thread thread 擷取同步狀态的線程
Node nextWaiter 條件隊列中的後續節點,如果目前節點是共享的,那麼這個字段将是一個SHARED變量,也就是說節點類型(獨占和共享)和條件隊列中的後續節點共用同一個字段
LockSupport void park() 阻塞目前線程,如果調用unpark方法或者目前線程被中斷,才能從park方法傳回
LockSupport void unpark(Thread thread) 喚醒處于阻塞狀态的線程
ConditionObject Node firstWaiter 條件隊列首節點
ConditionObject Node lastWaiter 條件隊列尾節點
void await()

目前線程進入等待狀态直到signal或中斷,目前線程将進入運作狀态且從await方法傳回的情況,包括:

其他線程調用該Condition的signal或者signalAll方法,且目前線程被選中喚醒;

其他線程調用interrupt方法中斷目前線程;

如果目前線程從await方法傳回表明該線程已經擷取了Condition對象對應的鎖

void awaitUninterruptibly() 和await方法類似,但是對中斷不敏感
long awaitNanos(long nanosTimeout) 目前線程進入等待狀态直到被signal、中斷或者逾時。傳回值表示剩餘的時間。
boolean awaitUntil(Date deadline) 目前線程進入等待狀态直到被signal、中斷或者某個時間。如果沒有到指定時間就被通知,方法傳回true,否則表示到了指定時間,傳回false
void signal() 喚醒一個等待在Condition上的線程,該線程從等待方法傳回前必須獲得與Condition相關聯的鎖
void signalAll() 喚醒所有等待在Condition上的線程,能夠從等待方法傳回的線程必須獲得與Condition相關聯的鎖

看到這,我們對AQS的資料結構應該基本上有一個大緻的認識,有了這個基本面的認識,我們就可以來看下AQS的源代碼。

AQS的源代碼實作

主要通過獨占式同步狀态的擷取和釋放、共享式同步狀态的擷取和釋放來看下AQS是如何實作的。

獨占式同步狀态的擷取和釋放

獨占式同步狀态調用的方法是acquire,代碼如下:

public final void acquire(int arg) {
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
           

上述代碼主要完成了同步狀态擷取、節點構造、加入同步隊列以及在同步隊列中自旋等待的相關工作,其主要邏輯是:首先調用子類實作的tryAcquire方法,該方法保證線程安全的擷取同步狀态,如果同步狀态擷取失敗,則構造獨占式同步節點(同一時刻隻能有一個線程成功擷取同步狀态)并通過addWaiter方法将該節點加入到同步隊列的尾部,最後調用acquireQueued方法,使得該節點以自旋的方式擷取同步狀态。如果擷取不到則阻塞節點中的線程,而被阻塞線程的喚醒主要依靠前驅節點的出隊或阻塞線程被中斷來實作。

下面來首先來看下節點構造和加入同步隊列是如何實作的。代碼如下:

private Node addWaiter(Node mode) {
        // 目前線程構造成Node節點
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        // 嘗試快速在尾節點後新增節點 提升算法效率 先将尾節點指向pred
        Node pred = tail;
        if (pred != null) {
            //尾節點不為空  目前線程節點的前驅節點指向尾節點
            node.prev = pred;
            //并發處理 尾節點有可能已經不是之前的節點 是以需要CAS更新
            if (compareAndSetTail(pred, node)) {
                //CAS更新成功 目前線程為尾節點 原先尾節點的後續節點就是目前節點
                pred.next = node;
                return node;
            }
        }
        //第一個入隊的節點或者是尾節點後續節點新增失敗時進入enq
        enq(node);
        return node;
    }
private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                //尾節點為空  第一次入隊  設定頭尾節點一緻 同步隊列的初始化
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                //所有的線程節點在構造完成第一個節點後 依次加入到同步隊列中
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }
           

節點進入同步隊列之後,就進入了一個自旋的過程,每個線程節點都在自省地觀察,當條件滿足,擷取到了同步狀态,就可以從這個自旋過程中退出,否則依舊留在這個自旋過程中并會阻塞節點的線程,代碼如下:

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                //擷取目前線程節點的前驅節點
                final Node p = node.predecessor();
                //前驅節點為頭節點且成功擷取同步狀态
                if (p == head && tryAcquire(arg)) {
                    //設定目前節點為頭節點
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //是否阻塞
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
           

再來看看shouldParkAfterFailedAcquire和parkAndCheckInterrupt是怎麼來阻塞目前線程的,代碼如下:

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        //前驅節點的狀态決定後續節點的行為
     int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*前驅節點為-1 後續節點可以被阻塞
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*前驅節點是初始或者共享狀态就設定為-1 使後續節點阻塞
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
private final boolean parkAndCheckInterrupt() {
        //阻塞線程
        LockSupport.park(this);
        return Thread.interrupted();
    }
           

節點自旋的過程大緻示意圖如下,其實就是對圖二、圖三的補充。

Java多線程 - AQS詳解

圖六  節點自旋擷取隊列同步狀态

整個獨占式擷取同步狀态的流程圖大緻如下:

Java多線程 - AQS詳解

圖七  獨占式擷取同步狀态

當同步狀态擷取成功之後,目前線程從acquire方法傳回,對于鎖這種并發元件而言,就意味着目前線程擷取了鎖。有擷取同步狀态的方法,就存在其對應的釋放方法,該方法為release,現在來看下這個方法的實作,代碼如下:

public final boolean release(int arg) {
        if (tryRelease(arg)) {//同步狀态釋放成功
            Node h = head;
            if (h != null && h.waitStatus != 0)
                //直接釋放頭節點
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*尋找符合條件的後續節點
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            //喚醒後續節點
            LockSupport.unpark(s.thread);
    }
           

獨占式釋放是非常簡單而且明确的。

總結下獨占式同步狀态的擷取和釋放:在擷取同步狀态時,同步器維護一個同步隊列,擷取狀态失敗的線程都會被加入到隊列中并在隊列中進行自旋;移出隊列的條件是前驅節點為頭節點且成功擷取了同步狀态。在釋放同步狀态時,同步器調用tryRelease方法釋放同步狀态,然後喚醒頭節點的後繼節點。

共享式同步狀态的擷取和釋放

共享式同步狀态調用的方法是acquireShared,代碼如下:

public final void acquireShared(int arg) {
        //擷取同步狀态的傳回值大于等于0時表示可以擷取同步狀态
        //小于0時表示可以擷取不到同步狀态  需要進入隊列等待
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }
private void doAcquireShared(int arg) {
        //和獨占式一樣的入隊操作
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            //自旋
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        //前驅結點為頭節點且成功擷取同步狀态 可退出自旋
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        //退出自旋的節點變成首節點
        setHead(node);
        /*
         * Try to signal next queued node if:
         *   Propagation was indicated by caller,
         *     or was recorded (as h.waitStatus either before
         *     or after setHead) by a previous operation
         *     (note: this uses sign-check of waitStatus because
         *      PROPAGATE status may transition to SIGNAL.)
         * and
         *   The next node is waiting in shared mode,
         *     or we don't know, because it appears null
         *
         * The conservatism in both of these checks may cause
         * unnecessary wake-ups, but only when there are multiple
         * racing acquires/releases, so most need signals now or soon
         * anyway.
         */
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }
           

與獨占式一樣,共享式擷取也需要釋放同步狀态,通過調用releaseShared方法可以釋放同步狀态,代碼如下:

public final boolean releaseShared(int arg) {
        //釋放同步狀态
        if (tryReleaseShared(arg)) {
            //喚醒後續等待的節點
            doReleaseShared();
            return true;
        }
        return false;
    }
private void doReleaseShared() {
        /*
         * Ensure that a release propagates, even if there are other
         * in-progress acquires/releases.  This proceeds in the usual
         * way of trying to unparkSuccessor of head if it needs
         * signal. But if it does not, status is set to PROPAGATE to
         * ensure that upon release, propagation continues.
         * Additionally, we must loop in case a new node is added
         * while we are doing this. Also, unlike other uses of
         * unparkSuccessor, we need to know if CAS to reset status
         * fails, if so rechecking.
         */
        //自旋
    for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    //喚醒後續節點
            unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }
           

unparkSuccessor方法和獨占式是一樣的。

3. AQS應用

AQS被大量的應用在了同步工具上。

  ReentrantLock:ReentrantLock類使用AQS同步狀态來儲存鎖重複持有的次數。當鎖被一個線程擷取時,ReentrantLock也會記錄下目前獲得鎖的線程辨別,以便檢查是否是重複擷取,以及當錯誤的線程試圖進行解鎖操作時檢測是否存在非法狀态異常。ReentrantLock也使用了AQS提供的ConditionObject,還向外暴露了其它監控和監測相關的方法。

  ReentrantReadWriteLock:ReentrantReadWriteLock類使用AQS同步狀态中的16位來儲存寫鎖持有的次數,剩下的16位用來儲存讀鎖的持有次數。WriteLock的建構方式同ReentrantLock。ReadLock則通過使用acquireShared方法來支援同時允許多個讀線程。

  Semaphore:Semaphore類(信号量)使用AQS同步狀态來儲存信号量的目前計數。它裡面定義的acquireShared方法會減少計數,或當計數為非正值時阻塞線程;tryRelease方法會增加計數,在計數為正值時還要解除線程的阻塞。

  CountDownLatch:CountDownLatch類使用AQS同步狀态來表示計數。當該計數為0時,所有的acquire操作(對應到CountDownLatch中就是await方法)才能通過。

  FutureTask:FutureTask類使用AQS同步狀态來表示某個異步計算任務的運作狀态(初始化、運作中、被取消和完成)。設定(FutureTask的set方法)或取消(FutureTask的cancel方法)一個FutureTask時會調用AQS的release操作,等待計算結果的線程的阻塞解除是通過AQS的acquire操作實作的。

  SynchronousQueues:SynchronousQueues類使用了内部的等待節點,這些節點可以用于協調生産者和消費者。同時,它使用AQS同步狀态來控制當某個消費者消費目前一項時,允許一個生産者繼續生産,反之亦然。

       除了這些j.u.c提供的工具,還可以基于AQS自定義符合自己需求的同步器。