AQS源码详解与实践

AQS源码详解
1. 概述

​ AQS(AbstractQueuedSynchronizer )字面意思是抽象队列同步器,是用来构建锁或者其它同步器组件的重量级基础框架及整个JUC体系的基石。通过内置的FIFO队列来完成资源获取线程的排队工作,并通过一个int类变量表示持有锁的状态。

​ AQS解决的问题主要是,在加锁的时候会产生阻塞,此时就需要排队。而实现排队就需要某种类型的队列去进行管理。并且排队的线程也需要被唤醒以保证锁的分配。这些都由AQS来进行解决。总结一下可以分为两点:

(1)对排队的线程进行管理。(2)唤醒排队的线程以确保锁的分配

官网的解释:
Provides a framework for implementing blocking locks and related synchronizers (semaphores, events, etc) that rely on first-in-first-out (FIFO) wait queues. 
提供一个框架来实现阻塞锁和依赖先进先出(FIFO)等待队列的相关同步器(信号量、事件等)。
2. AQS的结构

AQS主要分为两个主要部分:

  • 用一个volatile的int类型的成员变量state来表示同步状态

    /**
     * The synchronization state.
     */
    private volatile int state;
    
  • 一条基于CLH队列的变体实现的双向队列,用于排列被包装为Node节点的线程

    * <p>To enqueue into a CLH lock, you atomically splice it in as new
    * tail. To dequeue, you just set the head field.
    * <pre>
    *      +------+  prev +-----+       +-----+
    * head |      | <---- |     | <---- |     |  tail
    *      +------+       +-----+       +-----+
        static final class Node {
            /** Marker to indicate a node is waiting in shared mode */
            static final Node SHARED = new Node();
            /** Marker to indicate a node is waiting in exclusive mode */
            static final Node EXCLUSIVE = null;
    
            /** waitStatus value to indicate thread has cancelled */
            static final int CANCELLED =  1;
            /** waitStatus value to indicate successor's thread needs unparking */
            static final int SIGNAL    = -1;
            /** waitStatus value to indicate thread is waiting on condition */
            static final int CONDITION = -2;
            /**
             * waitStatus value to indicate the next acquireShared should
             * unconditionally propagate
             */
            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;
        }
    

关于state变量

​ state表示同步状态,state = 0表示锁空闲可以使用,而state >= 1表示锁被占用。整个state被volatile修饰,保证其可见性。

关于Node节点

​ 从上面的定义可以看出,Node节点里定义了状态位waitStatus,同样用volatile修饰,用于表示当前Node节点的状态。同时有一个前指针和后指针,表明这是一个双端队列。同时在AQS中还定义了一个头指针和尾指针,永远指向头结点和为尾节点。

/**
 * Head of the wait queue, lazily initialized.  Except for
 * initialization, it is modified only via method setHead.  Note:
 * If head exists, its waitStatus is guaranteed not to be
 * CANCELLED.
 */
private transient volatile Node head;

/**
 * Tail of the wait queue, lazily initialized.  Modified only via
 * method enq to add new wait node.
 */
private transient volatile Node tail;

因此我们可以画出AQS的简单结构图以供理解:
在这里插入图片描述

3. 源码实例理解(以ReentrantLock为例)

ReentrantLock默认为非公平锁,这里就涉及到公平锁和非公平锁的概念。公平锁和非公平锁的定义如下:

公平锁:公平锁讲究先来先到,线程在获取锁时,如果这个锁的等待队列中已经有线程在等待,那么当前线程就会进入等待队列中。

非公平锁:不管是否有等待队列,如果可以获取锁,则立刻占有锁对象。也就是说队列的第一个排队线程在unpark(), 之后还是需要竞争锁(存在线程竞争的情况下)

公平锁代码:

/**
     * Sync object for fair locks
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }

非公平锁代码:

/**
     * Sync object for non-fair locks
     */
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

 /**
  * Performs non-fair tryLock.  tryAcquire is implemented in
  * subclasses, but both need nonfair try for trylock method.
 */
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }

​ 对比公平锁和非公平锁的tryAcqure()方法的实现代码, 其实差别就在于非公平锁获取锁时比公平锁中少了一个判断!hasQueuedPredecessors()。hasQueuedPredecessors()主要是判断是否需要排队。这也是两者之间最大的区别。

​ 接下来我们可以从公平锁和非公平锁的lock()方法看起,公平锁的lock()方法就是直接调用acquire()方法,而非公平锁的lock()方法会先判断锁是否空闲,空闲则占有,反之也会调用acquire()方法。所以我们可以看到公平锁和非公平锁最终都会调用acquire()方法。

​ 因此我们可以看一下acquire()方法,具体如下:

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

​ 这边我们可以看到有三个方法,分别是tryAcquire()、addWaiter()和acquireQueued()方法。下面我们对每个方法进行具体的分析:

tryAcquire()方法(以非公平锁为例)

protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread(); //获取当前线程:此时是顾客B
    int c = getState(); //获取state状态,此时值为1
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

​ 通过nonfairTryAcquire()方法我们可以知道,首先会对整个AQS的state变量进行判断,如果为0,就意味着此时锁空闲,那就使用CAS尝试获取。获取成功则将当前线程设置为此时掌握锁的线程并返回true。若不为0,即此时锁已被占有,则会判断此时占有锁的线程是不是当前线程,是的话则计算state的值并进行后续判断,这一块主要是针对可重入锁的。若都不满足则返回false。

​ 我们再回到之前的这个判断语句,如下:

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

​ 当tryAcquire()方法返回true的时候,由于取反则返回false,根据&&的相关特性,如果为false则直接跳过后面的判断条件,会直接进行中断即获取到锁。若返回值为false,取反则为true,此时就要看后面的条件,即另外两个方法。

addWaiter()方法

/**
 * Creates and enqueues node for current thread and given mode.
 *
 * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
 * @return the new node
 */
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

​ 我们可以从源码看到addWaiter()方法是进行节点处理的,首先构建一个包含当前线程的Node节点,然后对尾指针进行判断,若为空说明队列里此时没有等待线程结点,此时会调用enq()方法,最后返回结点。

/**
 * Inserts node into queue, initializing if necessary. See picture above.
 * @param node the node to insert
 * @return node's predecessor
 */
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()方法的源码我们可以看到这是一种自旋操作,具体操作首先对尾指针进行判断,为空则建立一个新的Node()节点即一个傀儡节点,同时头尾指针都指向它。若不为空,则将节点挂到队列尾。再回到前面,我们可以看到当尾节点不为null的时候,就直接将节点挂到队列尾即可。这两部分代码都是一样的。

acquireQueued()方法

/**
 * Acquires in exclusive uninterruptible mode for thread already in
 * queue. Used by condition wait methods as well as acquire.
 *
 * @param node the node
 * @param arg the acquire argument
 * @return {@code true} if interrupted while waiting
 */
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);
    }
}

​ 再看最后一个方法,我们前面通过addWaiter()方法返回了一个Node结点,而acquireQueued()方法也是通过一个自旋操作对返回的节点进行判断。

/**
 * 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;
}

predecessor()方法就是得到当前节点的前驱节点,而判断条件

if (p == head && tryAcquire(arg))

​ 就是判断这个节点不是傀儡节点,并且此时已经成功获取到锁,此时会进行两个操作,一个就是将持有锁的线程设置为当前线程;另一个就是将队列中的此线程变成傀儡节点,并将傀儡节点删除以便后续的GC回收。而若果不满足这个条件则进入下一个判断

if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())

shouldParkAfterFailedAcquire()方法如下

/**
 * Checks and updates status for a node that failed to acquire.
 * Returns true if thread should block. This is the main signal
 * control in all acquire loops.  Requires that pred == node.prev.
 *
 * @param pred node's predecessor holding status
 * @param node the node
 * @return {@code true} if thread should block
 */
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        /*
         * 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 {
        /*
         * 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;
}

​ 可以看到这个方法会对每个Node结点中的waitStatus变量进行判断,不同的值代表不同的情况。针对不同的值会有不同的返回值。当ws大于1时表示当前线程获取锁的请求被取消了,我们可以看到节点会被删除,然后返回false,最终取消。当ws为Node.SIGNAL的时候,表示此时需要被挂起,返回true。我们再回到之前的判断条件,进入parkAndCheckInterrupt()方法

/**
 * Convenience method to park and then check if interrupted
 *
 * @return {@code true} if interrupted
 */
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

这个方法调用LockSupport.park()方法将此线程进行阻塞,只有当锁释放的时候,才能解开。我们再来看unlock方法

public void unlock() {
    sync.release(1);
}

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

protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;//此时c就为0
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null); //将当前拥有锁的线程设置为null
    }
    setState(c); //同时设置state值为0
    return free;
}

当调用unlock()方法时,能进入unparkSuccessor(h)方法:

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;  //此时为-1
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0); //进入,通过CAS操作将状态设置为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) //upark唤醒线程
        LockSupport.unpark(s.thread);
}

我们可以看到调用了LockSupport.unpark()方法,此时线程的阻塞状态才会被解除。再回到前面的代码:

final boolean acquireQueued(final Node node, int arg) { //顾客B
    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);
    }
}

​ 此时由于是一个自旋的操作,因此会再次尝试获取锁,若成功则返回false,不成功则再次进入阻塞状态等待唤醒。在回到最初的判断条件

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

当都为false的时候,由于都取反,因此都为true,此时产生中断,成功获取到锁。所有的过程到此完成。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值