Java并发编程学习(源码篇)|第1篇:AQS源码深度解析

背景

队列同步器AbstractQueuedSynchronizer,简称AQS,是用来构建锁或者其他同步组件的基础框架,以下代码上的中文注释大多为源代码中注释的翻译。

 


介绍

AQS提供了如下五个用于重写的方法:

  • protected boolean tryAcquire(int arg) 独占式的获取同步状态,返回值为true成功,false失败。

  • protected boolean tryRelease(int arg) 独占式的释放同步状态,返回值为true成功,false失败。

  • protected int tryAcquireShared(int arg) 共享式的获取同步状态,返回值为大于0成功,小于0失败

  • protected boolean tryReleaseShared(int arg) 共享式的释放同步状态,返回值为true成功,false失败。

  • protected boolean isHeldExclusively() 检查当前同步器是否在独占模式下被线程占用。

 

在自定义同步组件时,将会同步器提供的模板方法,如下为(部分)模板方法:

  • void acquire(int arg) 独占式的获取同步状态,阻塞

  • void acquireInterruptibly(int arg) 独占式的获取同步状态,阻塞,响应中断

  • boolean tryAcquireNanos(int arg, long nanosTimeout) 在acquireInterruptibly方法之上增加了超时等待,成功获取返回true,失败返回false

  • void acquireShared(int arg) 共享式的获取同步状态,阻塞。允许多个共享式的线程获取到同步状态

  • void acquireSharedInterruptibly(int arg) 与acquireShared类似,响应中断

  • boolean tryAcquireSharedNanos(int arg, long nanosTimeout) 在acquireSharedInterruptibly方法之上增加了超时等待,成功获取返回true,失败返回false

  • boolean release(int arg) 独占式的释放同步状态,会在释放同步状态之后,唤醒同步队列中第一个节点包含的线程

  • boolean releaseShared(int arg) 共享式的释放同步状态

  • Collection<Thread> getQueuedThreads() 获取等待在同步队列上的线程集合

 


 

源码解析

首先要先说明AQS中的两个队列:同步队列和等待队列

同步队列是用于存放获取同步状态失败的节点;等待队列是AQS中对Condition接口实现的一个内部类ConditionObject中的队列,用于存放等待获取监视器的节点。节点源码如下:

/**

* AQS内部维护了一个同步队列,Node就是同步队列的节点类,结构为:

*      +------+  prev +-----+       +-----+

* head |      | <---- |     | <---- |     |  tail

*      +------+       +-----+       +-----+

* 内部有一个head节点和tail节点,节点中存放前后节点的指针

*/

static final class Node {

    /** 标记该节点处于共享模式下等待 */

    static final Node SHARED = new Node();

    /** 标记该节点处于独占模式下等待 */

    static final Node EXCLUSIVE = null;



    /** waitStatus的值,表示线程处于退出状态 */

    static final int CANCELLED =  1;

    /** waitStatus的值,表示当前节点被释放后,后续节点的线程需要被唤醒 */

    static final int SIGNAL    = -1;

    /** waitStatus的值,表示线程等待在Condition上(即监视器) */

    static final int CONDITION = -2;

    /** waitStatus的值,表示后续的共享式获取同步状态不需要等待在Condition上,并传播 */

    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.

     *

     * 这个字段在普通的同步节点中会被初始化为0,在condition队列的节点中,会被初始化为CONDITION。

     * 这个值使用CAS进行更新(或者在可能的情况下,无条件的使用volatile写)

     */

    volatile int waitStatus;



    /**

     * 链接到当前节点/线程所依赖的用于检查waitStatus的前驱节点。在入队时分配,只有在

     * 出队时设置为null(为了GC考虑)。

     */

    volatile Node prev;



    /**

     * 链接到当前节点/线程release时会唤醒的后续节点

     */

    volatile Node next;



    /**

     * 入队节点的线程,在初始化时设置,使用后设置为null

     */

    volatile Thread thread;



    /**

     * 链接到下一个等待在等待队列上的节点, 或者一个特殊值SHARED。因为等待

     * 队列仅允许独占式的访问,我们仅需要一个简单的链接队列去持有等待在

     * condition上的node,它们后续会被移动到阻塞队列中重新获取同步状态。 

     * 因为condition是独占式使用的,所以我们使用一个特殊值表示共享状态。

     */

    Node nextWaiter;



    /**

     * 返回true则表示节点阻塞在共享模式

     */

    final boolean isShared() {

        return nextWaiter == SHARED;

    }



    /**

     * 返回前驱节点,为空抛出NullPointerException

     *

     * @return the predecessor of this node

     */

    final Node predecessor() throws NullPointerException {

        Node p = prev;

        if (p == null)

            throw new NullPointerException();

        else

            return p;

    }



    Node() {    // 用于创建初始化的head节点或者SHARED模式的标记

    }



    Node(Thread thread, Node mode) {     // 用于添加队列的后续节点

        this.nextWaiter = mode;

        this.thread = thread;

    }



    Node(Thread thread, int waitStatus) { // 用于Condition

        this.waitStatus = waitStatus;

        this.thread = thread;

    }

}

 

接下来对每个模板方法的源码进行分析:

 

void acquire(int arg) 模板方法

/**

* 独占式的获取同步状态,忽略中断。实现方式是至少执行一次tryAcquire方法(需要我们

* 自己实现的),如果tryAcquire返回true则成功,否则线程将阻塞,可能会重复多次加锁

* 与解锁,直到执行tryAcquire返回true。这个方法可以用于实现Lock的lock方法。

*/

public final void acquire(int arg) {

    if (!tryAcquire(arg) &&

        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

        selfInterrupt();

}

可以看到,如果第一次执行tryAcquire成功,则方法直接返回,如果失败,则会执行acquireQueued方法。我们可以看到,acquireQueued方法的参数中传入的是addWaiter方法的返回值,所以这里我们先看一下addWaiter方法

 

Node addWaiter(Node mode)

/**

* 为当前的线程创建一个入队节点,并设置当前的节点模式

*

* @param mode Node.EXCLUSIVE 为独占模式, Node.SHARED 为共享模式

* @return 新创建的入队节点

*/

private Node addWaiter(Node mode) {

    Node node = new Node(Thread.currentThread(), mode);

    // 尝试快速入队,如果失败则使用完整的入队方法(即下面的enq方法)

    Node pred = tail;

    if (pred != null) {

        node.prev = pred;

        if (compareAndSetTail(pred, node)) {

            pred.next = node;

            return node;

        }

    }

    enq(node);

    return node;

}

addWaiter方法首先会为当前线程创建一个node,并设置一个mode。然后对这个节点尝试快速的入队:

1.如果tail节点不为空,则将tail节点cas替换成当前节点;

2.并将原tail节点的next节点设置为新创建的这个node;

3.如果失败,则调用完整的入队方法,即enq。

 

我们继续看一下enq方法

Node enq(final Node node)

/**

* 向队列中插入节点,如果需要则进行初始化

* @param 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;

            }

        }

    }

}

enq方法首先会判断tail节点是否为空,这也是addWaiter方法会判断的,只不过addWaiter方法在tail为空的情况下直接就交给enq方法处理了。如果tail节点为空,则表示当前的同步队列为空,需要对当前的同步队列进行初始化。初始化会cas的设置head节点,并将tail节点指向head节点,然后进入下一次循环。在队列初始化完之后,tail已经不为空了,那么会将插入node的prev指向原tail节点,并cas的替换tail节点为当前节点,如果替换成功,则将原tail节点的next节点指向当前节点,并返回原tail节点。值得注意的是,这里使用了CAS+自旋的方式保证了节点入队的成功,并只有在cas替换tail节点成功后,才会将原tail节点指向插入节点。

 

介绍完addWaiter方法之后,我们继续去看acquireQueued方法

 

boolean acquireQueued(final Node node, int arg)

/**

* 对已入队的线程独占且不响应中断式的获取同步状态,使用条件判断和阻塞方法。

*

* @param node the node

* @param arg the acquire argument

* @return 线程等待时被中断返回true

*/

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);

    }

}

在acquireQueued中,会自旋的判断node的前驱节点是否为head节点,如果是则尝试获取同步状态。成功或者则将node设置为head节点,失败则通过shouldParkAfterFailedAcquire判断线程是否需要阻塞。

 

boolean shouldParkAfterFailedAcquire(Node pred, Node node)

/**

* 在获取同步状态失败后检查并更新节点的状态。返回值为true则代表线程需要阻塞。

* 这是所有的获取同步的循环中都很重要的一个唤醒控制方法。参数pred == node.prev

*

* @param pred node's predecessor holding status

* @param node the node

* @return {@code true} 如果线程需要阻塞

*/

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {

    int ws = pred.waitStatus;

    if (ws == Node.SIGNAL)

        /*

         * 这个节点已经设置了release后被唤醒的状态,所以可以安全的阻塞

         */

        return true;

    if (ws > 0) {

        /*

         * 前驱节点已经处于CANCELLED状态,覆盖当前的前驱节点并重试

         */

        do {

            node.prev = pred = pred.prev;

        } while (pred.waitStatus > 0);

        pred.next = node;

    } else {

        /*

         * waitStatus一定为0或者PROPAGATE。表示我们现在需要一个被唤醒的信号,

         * 但现在还不需要被阻塞。调用者需要重试,以确保在阻塞前还无法获取同步状态

         */

        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);

    }

    return false;

}

若需要阻塞,则调用parkAndCheckInterrupt阻塞并检查中断

 

boolean parkAndCheckInterrupt()

/**

* 一个提供阻塞并检查中断的简便方法

*

* @return {@code true} if interrupted

*/

private final boolean parkAndCheckInterrupt() {

    LockSupport.park(this);

    return Thread.interrupted();

}

 

boolean release(int arg)模板方法

/**

* 独占式的释放同步状态。实现基于当tryRelease方法返回true时,对一个

* 或者多个线程进行解锁。这个方法可以用于实现Lock的unlock方法。

*/

public final boolean release(int arg) {

    if (tryRelease(arg)) {

        Node h = head;

        if (h != null && h.waitStatus != 0)

            unparkSuccessor(h);

        return true;

    }

    return false;

}

release方法相对于acquire方法就简单的多了。首先调用tryRelease尝试释放,如果此时头节点不为空且头节点的h.waitStatus != 0则说明同步队列中已有节点被阻塞,唤醒后继节点并返回true。若tryRelease返回false,则直接返回false。

 

void unparkSuccessor(Node node)

/**

* 如果有后继节点,唤醒它

*

* @param node the node

*/

private void unparkSuccessor(Node node) {

    /*

     * 如果节点状态为负,尝试清除预先的状态。这个操作允许失败。

     */

    int ws = node.waitStatus;

    if (ws < 0)

        compareAndSetWaitStatus(node, ws, 0);



    /*   

     * 唤醒保存在后续节点的线程,正常情况下就是next节点。但如果它为

     * null或者处于CANCELLED状态,则从tail节点向前遍历寻找不为null且

     * 不处于CANCELLED状态的节点进行唤醒

     */

    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);

}

当node的后继节点不为null或者CANCELLED状态时,是直接将其唤醒的。如果处于这两种状态,则会从tail向前遍历,并将需唤醒的节点t替换为一个可唤醒的节点。这里需要注意的是,遍历会一直循环到同步队列为空或者重新指向当前节点为止,所以被唤醒的节点还是空间上最接近node的节点。

 


 

void acquireShared(int arg) 模板方法

/**

* 共享式的获取同步状态,忽略中断。实现方式是第一次,并至少执行一次tryAcquireShared

* 如果失败则线程入队。可能会重复的加锁和加锁,直到执行tryAcquireShared成功

*/

public final void acquireShared(int arg) {

    if (tryAcquireShared(arg) < 0)

        doAcquireShared(arg);

}

方法内的逻辑比较简单,注释也比较详细,就不做过多的赘述。接下来看一下doAcquireShared方法

 

void doAcquireShared(int arg)

/**

* 共享式的获取同步状态并忽略中断

* @param arg the acquire argument

*/

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);

    }

}

可以看到,doAcquireShared和独占式获取节点同步状态的acquireQueued方法很类似,重复的代码就不再赘述,这里我们重点看下setHeadAndPropagate方法

 

void setHeadAndPropagate(Node node, int propagate)

/**

* 为队列设置head节点,如果并检查后继节点是否阻塞在共享模式。如果propagate > 0

* 或PROPAGATE状态被设置,则传播

*

* @param node 

* @param propagate tryAcquireShared的返回值

*/

private void setHeadAndPropagate(Node node, int propagate) {

    Node h = head;

    setHead(node);

    /*

     * 满足以下条件则唤醒后续的节点:

     *   调用者表明需要传播,

     *   或者已被之前的操作覆盖(h.waitStatus在setHead之前或者之后

     *     (注意:这里对waitStatus使用标记-检查是因为PROPAGATE状态

     *      可能被转换成SIGNAL状态.)

     * 并且

     *   下一个节点在共享模式下阻塞,如果我们不知道它的状态,那么是因为它为null

     *

     * 这种保守方式的检查可能会导致不需要的唤醒,但只有当多个线程同时

     * 竞争。所以无论如何大多数线程还是需要在现在或者未来被唤醒的。

     */

    if (propagate > 0 || h == null || h.waitStatus < 0 ||

        (h = head) == null || h.waitStatus < 0) {

        Node s = node.next;

        if (s == null || s.isShared())

            doReleaseShared();

    }

}

这里使用了相对保守的方式对节点的状态进行了检查。其实我们可以看到,如果是共享锁获取锁状态成功则propagate > 0。那么会判断head的后继节点是否为共享节点,如果是,则执行doReleaseShared方法。

 

void doReleaseShared()

/**

* 共享模式下的释放操作 -- 唤醒后续节点并确保传播继续。(注意,对独占模式,

* release仅相当于在需要被唤醒时,对head节点调用了unparkSuccessor方法)

*/

private void doReleaseShared() {

    /*

     * 确保释放的传递,即使其他线程调用了acquires/releases方法。通常情况下,

     * 如果节点处于SIGNAL,则对head调用unparkSuccessor方法。但如果

     * 不是的话,状态将被设置PROPAGATE去确保它将被释放,传播会继续。另外,

     * 我们必须要对其进行循环,因为我们在操作时可能有新节点被添加。同时,不像

     * 其他调用unparkSuccessor的方法,我们需要知道CAS设置状态是否失败,如果失败

     * 则需要重试.

     */

    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;

    }

}

如果头节点的状态为SIGNAL,则CAS的将节点状态替换为0,并释放后继节点,失败则重试。确保传播的继续。

 


 

boolean releaseShared(int arg) 模板方法

​
/**

* 共享模式下释放。实现基于如果tryReleaseShared返回值为true时解锁一个或者多个线程

*/

public final boolean releaseShared(int arg) {

    if (tryReleaseShared(arg)) {

        doReleaseShared();

        return true;

    }

    return false;

}

​

doReleaseShared上面已解释过,如果等待队列中有后续节点,则唤醒。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值