AbstractQueuedSynchronizer(AQS)源碼解析

前言

jdk1.6之前synchronized性能一直較為低下;jdk1.6之后,java開發(fā)團(tuán)隊(duì)對(duì)synchronized進(jìn)行了大量的優(yōu)化,其性能也提升了很多。但是與Lock系列鎖相比,缺少了獲取/釋放鎖的可操作性,可中斷和超時(shí)處理等特性。另外,它只支持獨(dú)占鎖,不支持共享鎖。

說到Lock,不得不說其依賴的AQS,它是非常非常重要的一個(gè)鎖抽象。

那么AQS又是什么呢?
AQS,英文名為AbstractQueuedSynchronizer,也叫隊(duì)列同步器,是構(gòu)建同步鎖或其他并發(fā)框架的基礎(chǔ),Doug Lea希望它能夠成為實(shí)現(xiàn)大部分同步需求的基礎(chǔ)和抽象。AQS解決了鎖實(shí)現(xiàn)的大部分細(xì)節(jié),如同步狀態(tài)的獲取、FIFO隊(duì)列。

AQS的使用主要是通過繼承的方式,然后繼承者被同步器所組合依賴。

主要方法

int getState() // 返回同步狀態(tài)的當(dāng)前值
void setState(int newState) // 設(shè)置同步狀態(tài)
boolean compareAndSetState(int expect, int update) // 使用CAS設(shè)置當(dāng)前同步狀態(tài)
void acquire(int arg) // 以獨(dú)占的方式獲取同步狀態(tài)
void acquireInterruptibly(int arg) throws InterruptedException // 以獨(dú)占的方式獲取同步狀態(tài)(可中斷)
void acquireShared(int arg) // 以共享的方式獲取同步狀態(tài)
void acquireSharedInterruptibly(int arg) throws InterruptedException // 以共享的方式獲取同步狀態(tài)(可中斷)
int tryAcquireShared(int arg) // 以共享的方式獲取同步狀態(tài)(非阻塞,需要繼承者實(shí)現(xiàn))
boolean tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException // 以共享的方式獲取同步狀態(tài)(帶超時(shí))
boolean isHeldExclusively() // 當(dāng)前線程是否持有同步狀態(tài)
boolean release(int arg) // 釋放同步狀態(tài)(獨(dú)占式)
boolean releaseShared(int arg) // 釋放同步狀態(tài)(共享式)
boolean tryAcquire(int arg) // 以獨(dú)占的方式獲取同步狀態(tài)(非阻塞,需要繼承者實(shí)現(xiàn))
boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException // 以獨(dú)占的方式獲取同步狀態(tài)(帶超時(shí))
boolean tryRelease(int arg) // 以獨(dú)占的方式釋放同步狀態(tài)(非阻塞,需要繼承者實(shí)現(xiàn))
boolean tryReleaseShared(int arg)  // 以共享的方式釋放同步狀態(tài)(非阻塞,需要繼承者實(shí)現(xiàn))

CLH隊(duì)列

CLH是一個(gè)FIFO(先進(jìn)先出的隊(duì)列),AQS依賴其來完成同步狀態(tài)的管理。當(dāng)前線程如果獲取同步狀態(tài)失敗,AQS會(huì)將當(dāng)前線程及等待狀態(tài)等組裝成一個(gè)Node,并加入到CLH隊(duì)列,同時(shí)會(huì)阻塞當(dāng)前線程,當(dāng)同步狀態(tài)釋放時(shí),會(huì)將首節(jié)點(diǎn)喚醒,并使其重新嘗試獲取同步狀態(tài)。

我們來看一下CLH中的一個(gè)Node的信息有哪些

static final class Node {
    // 標(biāo)識(shí)節(jié)點(diǎn)是否共享mode
    static final Node SHARED = new Node();
    // 標(biāo)識(shí)節(jié)點(diǎn)是否獨(dú)占mode
    static final Node EXCLUSIVE = null;

    /** 取消狀態(tài),線程被中斷 */
    static final int CANCELLED =  1;
    /** 通知狀態(tài),繼任節(jié)點(diǎn)需要被喚醒 */
    static final int SIGNAL    = -1;
    /** 帶條件登臺(tái)狀態(tài) */
    static final int CONDITION = -2;
    /** 下一次獲取同步狀態(tài)(共享)應(yīng)該無條件傳播 */
    static final int PROPAGATE = -3;

    /**
     * Status field, taking on only the values:
     *   SIGNAL:     The successor of this node is (or will soon be)
     *               blocked (via park), so the current node must
     *               unpark its successor when it releases or
     *               cancels. To avoid races, acquire methods must
     *               first indicate they need a signal,
     *               then retry the atomic acquire, and then,
     *               on failure, block.
     *   CANCELLED:  This node is cancelled due to timeout or interrupt.
     *               Nodes never leave this state. In particular,
     *               a thread with cancelled node never again blocks.
     *   CONDITION:  This node is currently on a condition queue.
     *               It will not be used as a sync queue node
     *               until transferred, at which time the status
     *               will be set to 0. (Use of this value here has
     *               nothing to do with the other uses of the
     *               field, but simplifies mechanics.)
     *   PROPAGATE:  A releaseShared should be propagated to other
     *               nodes. This is set (for head node only) in
     *               doReleaseShared to ensure propagation
     *               continues, even if other operations have
     *               since intervened.
     *   0:          None of the above
     *
     * The values are arranged numerically to simplify use.
     * Non-negative values mean that a node doesn't need to
     * signal. So, most code doesn't need to check for particular
     * values, just for sign.
     *
     * The field is initialized to 0 for normal sync nodes, and
     * CONDITION for condition nodes.  It is modified using CAS
     * (or when possible, unconditional volatile writes).
     */
    volatile int waitStatus;

    /**
     * Link to predecessor node that current node/thread relies on
     * for checking waitStatus. Assigned during enqueuing, and nulled
     * out (for sake of GC) only upon dequeuing.  Also, upon
     * cancellation of a predecessor, we short-circuit while
     * finding a non-cancelled one, which will always exist
     * because the head node is never cancelled: A node becomes
     * head only as a result of successful acquire. A
     * cancelled thread never succeeds in acquiring, and a thread only
     * cancels itself, not any other node.
     */
    volatile Node prev;

    /**
     * Link to the successor node that the current node/thread
     * unparks upon release. Assigned during enqueuing, adjusted
     * when bypassing cancelled predecessors, and nulled out (for
     * sake of GC) when dequeued.  The enq operation does not
     * assign next field of a predecessor until after attachment,
     * so seeing a null next field does not necessarily mean that
     * node is at end of queue. However, if a next field appears
     * to be null, we can scan prev's from the tail to
     * double-check.  The next field of cancelled nodes is set to
     * point to the node itself instead of null, to make life
     * easier for isOnSyncQueue.
     */
    volatile Node next;

    /**
     * The thread that enqueued this node.  Initialized on
     * construction and nulled out after use.
     */
    volatile Thread thread;

    /**
     * Link to next node waiting on condition, or the special
     * value SHARED.  Because condition queues are accessed only
     * when holding in exclusive mode, we just need a simple
     * linked queue to hold nodes while they are waiting on
     * conditions. They are then transferred to the queue to
     * re-acquire. And because conditions can only be exclusive,
     * we save a field by using special value to indicate shared
     * mode.
     */
    Node nextWaiter;

    /**
     * Returns true if node is waiting in shared mode.
     */
    final boolean isShared() {
        return nextWaiter == SHARED;
    }

    /**
     * Returns previous node, or throws NullPointerException if null.
     * Use when predecessor cannot be null.  The null check could
     * be elided, but is present to help the VM.
     *
     * @return the predecessor of this node
     */
    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }

    Node() {    // Used to establish initial head or SHARED marker
    }

    Node(Thread thread, Node mode) {     // Used by addWaiter
        this.nextWaiter = mode;
        this.thread = thread;
    }

    Node(Thread thread, int waitStatus) { // Used by Condition
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
}

CLH同步隊(duì)列結(jié)構(gòu)圖

image.png

入隊(duì)列

我們看看AQS中入隊(duì)列的代碼

private Node addWaiter(Node mode) {
    // 新建一個(gè)節(jié)點(diǎn),mode可以是獨(dú)占式,也可以是共享式
    Node node = new Node(Thread.currentThread(), mode);

    // 下面這段代碼會(huì)嘗試添加一個(gè)尾節(jié)點(diǎn)
    // 1. 將舊的tail節(jié)點(diǎn)賦值為pred
    // 2. 舊的tail尾節(jié)點(diǎn)不為null,則new節(jié)點(diǎn)的prev指向舊的tail節(jié)點(diǎn)
    // 3. 使用CAS設(shè)置new節(jié)點(diǎn)為tail節(jié)點(diǎn)
    // 4. 如果設(shè)置新節(jié)點(diǎn)為tail節(jié)點(diǎn)成功之后,則將舊的tail節(jié)點(diǎn)的next指向new節(jié)點(diǎn)
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    // 在tail節(jié)點(diǎn)為null或者將新節(jié)點(diǎn)設(shè)置為tail節(jié)點(diǎn)失敗時(shí),通過自旋的方式再次設(shè)置tail節(jié)點(diǎn)。
    // 這兒需要特別注意,為什么設(shè)置tail節(jié)點(diǎn)會(huì)有失敗的可能,因?yàn)閍ddWaiter方法可能會(huì)同時(shí)被多個(gè)線程調(diào)用
    enq(node);
    return node;
}

在初次嘗試如隊(duì)列失敗后,會(huì)調(diào)用enq方法,我們進(jìn)一步分析此方法的代碼

private Node enq(final Node node) {
    // for死循環(huán),其實(shí)就是自旋操作
    for (;;) {
        Node t = tail;
        // tail == null表示是第一次添加節(jié)點(diǎn)
        if (t == null) { // Must initialize
            // 1. CAS設(shè)置head節(jié)點(diǎn)可能會(huì)失敗。如果失敗,表示其他線程已經(jīng)設(shè)置head節(jié)點(diǎn)成功了
            // 2. 這兒設(shè)置了一個(gè)空的head節(jié)點(diǎn),后面我們會(huì)分析一下為啥需要設(shè)置一個(gè)空的head節(jié)點(diǎn)
            // 3. 在設(shè)置head節(jié)點(diǎn)成功之后,仍然需要再次執(zhí)行下面的else邏輯進(jìn)行尾節(jié)點(diǎn)的設(shè)置
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            // 這兒其實(shí)和AddWaiter的邏輯是一樣的,通過CAS設(shè)置尾節(jié)點(diǎn)
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

如隊(duì)列的整個(gè)過程圖如下:


image.png

同步狀態(tài)獲取與釋放

AQS內(nèi)部使用了模板方法模式來設(shè)計(jì),子類通過繼承的方式實(shí)現(xiàn)抽象方法(不完全是抽象方法,其實(shí)是一個(gè)普通的方法,父類默認(rèn)實(shí)現(xiàn)為throwException的方式)。主要的抽象方法有下面幾個(gè)

  1. 獨(dú)占式獲取同步狀態(tài)
  2. 獨(dú)占式釋放同步狀態(tài)
  3. 共享式獲取同步狀態(tài)
  4. 共享式釋放同步狀態(tài)
  5. 當(dāng)前線程是否獲取到了同步狀態(tài)

獨(dú)占式獲取

什么叫獨(dú)占式呢?其實(shí)就是同一時(shí)刻至多只能有一個(gè)線程獲取到同步狀態(tài)。

我們看看獨(dú)占式獲取同步狀態(tài)的代碼,acquire(int arg)

public final void acquire(int arg) {
    // 1. 如果沒有獲取到同步狀態(tài),則新創(chuàng)建一個(gè)獨(dú)占式節(jié)點(diǎn),并將其添加到同步隊(duì)列。
    // 2. 否則認(rèn)為已經(jīng)獲取到同步狀態(tài)成功了,當(dāng)前線程就可以執(zhí)行自身的邏輯了。
    // 3. 該方法不會(huì)響應(yīng)中斷,但是如果在執(zhí)行的過程中有過中斷操作,則會(huì)設(shè)置當(dāng)前線程的中斷狀態(tài)為true
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

我們前面已經(jīng)分析過了addWaiter方法,下面我們分析一下acquireQueued方法。

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false; // 是否有過中斷
        // 這兒有自旋操作
        for (;;) {
            // 如果當(dāng)前節(jié)點(diǎn)的前任是head節(jié)點(diǎn),則嘗試獲取同步狀態(tài),若獲取成功,則執(zhí)行下面操作:
            // 1. 設(shè)置當(dāng)前節(jié)點(diǎn)為head節(jié)點(diǎn)
            // 2. 前任節(jié)點(diǎn)的next指向null,那么前任節(jié)點(diǎn)就沒有依賴存活的對(duì)象了,可以更快得到GC
            // 3. 返回是否有過中斷的標(biāo)記
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // 當(dāng)前任節(jié)點(diǎn)不是head節(jié)點(diǎn),或者獲取同步狀態(tài)失敗。則會(huì)通過`park操作`讓`當(dāng)前線程等待`,讓出CPU。我們后面會(huì)分析這兩個(gè)方法,這兒暫時(shí)跳過
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        // 如果失敗過,則會(huì)對(duì)當(dāng)前節(jié)點(diǎn)進(jìn)行同步狀態(tài)的取消獲取操作
        if (failed)
            cancelAcquire(node);
    }
}

acquire方法的流程圖如下

image.png

我們接著看一下acquireInterruptibly方法,該方法在獲取同步狀態(tài)的時(shí)候,可以響應(yīng)中斷

public final void acquireInterruptibly(int arg) throws InterruptedException {
    // 線程中斷了,直接拋出異常
    if (Thread.interrupted())
        throw new InterruptedException();
    // 如果嘗試獲取同步狀態(tài)失敗,則執(zhí)行doAcquireInterruptibly方法
    if (!tryAcquire(arg))
        doAcquireInterruptibly(arg);
}

接著,我們看看doAcquireInterruptibly方法,該方法和acquireQueued基本一樣,唯一的兩點(diǎn)差別如下:

  1. 方法聲明上添加了throws InterruptedException
  2. 在park的時(shí)候,如果檢查到中斷,則拋出異常throw new InterruptedException()
private void doAcquireInterruptibly(int arg) throws InterruptedException {
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

接著我們看看超時(shí)獲取同步狀態(tài)的方法,tryAcquireNanos

public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {
    // 有過中斷,直接拋出異常
    if (Thread.interrupted())
        throw new InterruptedException();
    // 嘗試獲取同步狀態(tài)失敗時(shí),則執(zhí)行方法doAcquireNanos
    return tryAcquire(arg) ||
        doAcquireNanos(arg, nanosTimeout);
}

我們接著分析一下doAcquireNanos

private boolean doAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {
    // 如果超時(shí)納秒數(shù)不是正數(shù),則直接返回未獲取到同步狀態(tài)
    if (nanosTimeout <= 0L)
        return false;
    // 當(dāng)前時(shí)間+超時(shí)時(shí)間=獲取同步狀態(tài)的截止時(shí)間
    final long deadline = System.nanoTime() + nanosTimeout;
    // 創(chuàng)建一個(gè)獨(dú)占式的節(jié)點(diǎn)
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        // 自旋操作
        for (;;) {
            // 這兒和acquireQueued中相應(yīng)的代碼一模一樣
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return true;
            }
            // 這兒再次判斷是否超過截止時(shí)間
            nanosTimeout = deadline - System.nanoTime();
            if (nanosTimeout <= 0L)
                return false;
            // 和acquireQueued不同的地方有兩點(diǎn)
            // 1. park方法為帶毫秒的阻塞(超過指定時(shí)間則不再wait,繼續(xù)執(zhí)行)
            // 2. 如果離截止時(shí)間不超過1微妙,為了性能考慮,不再parkNanos了,而是for死循環(huán)。即使使用parkNanos也不準(zhǔn)確。spinForTimeoutThreshold為1000(1000納秒==1微秒)
            if (shouldParkAfterFailedAcquire(p, node) &&
                nanosTimeout > spinForTimeoutThreshold)
                LockSupport.parkNanos(this, nanosTimeout);
            // 同中斷的方法一樣,超時(shí)也會(huì)響應(yīng)中斷
            if (Thread.interrupted())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

整體流程圖如下:


image.png

獨(dú)占式釋放同步狀態(tài)方法只有一個(gè)——release

public final boolean release(int arg) {
    // 嘗試釋放同步狀態(tài),若沒成功,則返回false
    if (tryRelease(arg)) {
        Node h = head;
        // head節(jié)點(diǎn)不為null,且waitStatus不是初始狀態(tài),則unpark繼任節(jié)點(diǎn)
        if (h != null && h.waitStatus != 0)
           // 這兒會(huì)喚醒繼任節(jié)點(diǎn)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

共享式

我們接著分析共享式獲取同步狀態(tài)方法

public final void acquireShared(int arg) {
    // tryAcquireShared返回值>=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();
            // 前任節(jié)點(diǎn)為head節(jié)點(diǎn),嘗試獲取共享的同步狀態(tài)
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            // 前任節(jié)點(diǎn)不是head節(jié)點(diǎn),需要park操作
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

阻塞和喚醒線程

不管是共享式,還是獨(dú)占式,都會(huì)調(diào)用下面的邏輯,我們需要分析其中關(guān)鍵的兩個(gè)方法

if (shouldParkAfterFailedAcquire(p, node) &&
    parkAndCheckInterrupt())
    interrupted = true;
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    // 前驅(qū)節(jié)點(diǎn)為SIGNAL,表示需要等待,直接返回true
    if (ws == Node.SIGNAL)
        return true;
    // 前驅(qū)節(jié)點(diǎn)的狀態(tài)>0,表示為CANCELED,表示為已中斷的線程,需要從同步隊(duì)列中移除
    if (ws > 0) {
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    // 前驅(qū)節(jié)點(diǎn)狀態(tài)不為SINGNAL和CANCELED(則為CONDITION和PROPAGATE),則需要將前驅(qū)節(jié)點(diǎn)的狀態(tài)設(shè)置為SIGNAL
    } else {
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

接著我們分析一下方法parkAndCheckInterrupt,很簡(jiǎn)單,直接通過park方式使當(dāng)前線程變?yōu)閃AITING狀態(tài),然后返回線程的中斷標(biāo)記

private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

線程釋放同步狀態(tài)后,需要喚醒繼任節(jié)點(diǎn),我們分析一下釋放同步狀態(tài)的邏輯

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
           // 喚醒繼任節(jié)點(diǎn)
            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;
    // 節(jié)點(diǎn)狀態(tài)為負(fù)數(shù),則設(shè)置為0
    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;
    // 后繼節(jié)點(diǎn)為null或者其狀態(tài) > 0 (超時(shí)或者被中斷了)
    if (s == null || s.waitStatus > 0) {
        s = null;
        // 從尾節(jié)點(diǎn)往前找未被中斷的節(jié)點(diǎn)
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}

總結(jié)

至此,我們分析完了整個(gè)AQS的幾個(gè)關(guān)鍵的方法,包括入隊(duì)列、獲取同步狀態(tài)(獨(dú)占式和共享式)、釋放同步狀態(tài)(獨(dú)占式和共享式)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。