AbstractQueuedSynchronizer源码分析(一)---加锁

38 阅读5分钟

在分析 Java 并发包 java.util.concurrent 源码的时候,少不了需要了解 AbstractQueuedSynchronizer(以下简写AQS)这个抽象类,因为它是 Java 并发包的基础工具类,是实现 ReentrantLock、CountDownLatch、Semaphore、FutureTask 等类的基础。 下面我们开始分析通过ReentrantLock来分析AQS的源码,当然主要是通过FairSync来分析。

首先我们来看看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;

/**
 * The synchronization state.
 */
 // 是否持有锁的状态,=0表示没有线程持有锁,>0表示持有锁(为什么可以大于1?因为有可重入锁的存在)
private volatile int state;

以上就是AQS的四个属性

AQS通过Node这个静态内部类实现了一个双向的阻塞队列,当多线程来获取锁的时候,没有抢到锁的线程都是挂起在这个队列中。强调一下,head节点不在阻塞队列中,head节点不在阻塞队列中,head节点不在阻塞队列中。

image.png 下面我们来看看Node的属性

// 共享模式
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 CANCELLED    = -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;

// 当前节点的状态,包括上边四个(CANCELLED,CANCELLED,CONDITION,PROPAGATE)装填,加一个默认值0
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;

下面我们通过ReentrantLock来分析AQS。ReentrantLock内部通过维护的Sync来实现AbstractQueuedSynchronizer。Sync有两个实现类:FairSync(公平锁),NonfairSync(非公平锁)

ReentrantLock默认使用非公平锁,除非手动指定。


/**
 * Creates an instance of {@code ReentrantLock}.
 * This is equivalent to using {@code ReentrantLock(false)}.
 */
public ReentrantLock() {
    sync = new NonfairSync();
}

/**
 * Creates an instance of {@code ReentrantLock} with the
 * given fairness policy.
 *
 * @param fair {@code true} if this lock should use a fair ordering policy
 */
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

本文主要通过FairSync。

// 第一步,调用lock()方法
final void lock() {
    acquire(1);
}

// AbstractQueuedSynchronizer的acquire方法,tryAcquire方法就要看子类的实现了。
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

// 方法的入参acquires=1
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    // 获取当前锁状态,如果是第一个现场进来,这个时候status=0,还没有线程拿到锁
    int c = getState();
    if (c == 0) {
        // 不能直接抢锁,先要判断阻塞队列中是否已经有线程在排队了。因为是公平锁,所以每个新线程都需要入阻塞队列,先排队的先执行。
        if (!hasQueuedPredecessors() &&
            // 表示阻塞队列为空,可以尝试获取锁
            compareAndSetState(0, acquires)) {
            // 获取锁,标记锁为当前线程独占。
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 当前已经有线程获取锁了,判断一下获取锁的线程是否是当前线程,因为ReentrantLock是可重入锁
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}
public final boolean hasQueuedPredecessors() {
    // The correctness of this depends on head being initialized
    // before tail and on head.next being accurate if the current
    // thread is first in queue.
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    // 什么时候return true?head节点已经创建,并且(要么阻塞队列为空,要么阻塞队列第一个节点就是当前节点)。什么时候return false?要么head节点都没有,或者,阻塞队列不为空,并且队列中第一个节点不是当前节点。
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}


public final void acquire(int arg) {
    // 如果tryAcquire返回true表示拿到锁了,if判断不成立,线程继续执行自己的业务,如果没有拿到锁,则需要执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg),追加到队列中。先执行addWaiter(Node.EXCLUSIVE)方法。
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
// 需要注意Node的模式是独占模式
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指向老尾结点,自旋设置当前Node为新的尾结点。
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            // 将老尾结点的next指向新节点。形成双向的链表。当前的Node就加入阻塞队列了。
            pred.next = node;
            return node;
        }
    }
    // 如果执行到这里,表示之前的尾结点为空或者自旋设置当前Node为尾结点失败。当前Node还未入队。
    enq(node);
    return node;
}
// 循环将当前线程入队
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            // 表示当前队列是空的,新创建一个head节点,并将head节点也设置为tail节点。注意新创建的Node的waitStatus=0。然后进入下一次循环。此时tail就不为空了。

            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            // 下边就是将Node入队,并且设置当前Node为新的tail节点。
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

// 此时Node已经入队成功了。接着往下执行
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            // 如果前一个节点是head节点,可以尝试获取锁,因为如果head节点是新创建的,此时head还没有Thread。
            if (p == head && tryAcquire(arg)) {
                // 如果获取锁成功,设置当前节点为head
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // 走到这里表示获取锁失败了,线程就应该挂起在这里了。记住这里,阻塞队列中的线程是挂起在这里,当前线程被唤醒,是需要从这里继续往下执行的。shouldParkAfterFailedAcquire,返回false,则进入下一次循环,返回true,执行parkAndCheckInterrupt方法。这个方法一般都是返回false的,
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    // 获取前一节点的状态,
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        // 如果等于-1,就直接返回true,表示前一个节点是正常的,当前节点就需要挂起。
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         */
        return true;
    if (ws > 0) {
        // 如果大于0,表示取消了锁等待。则需要往前找小于0的节点
        /*
         * Predecessor was cancelled. Skip over predecessors and
         * indicate retry.
         */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        // 等于=0,或者-2,-3。初始就是等于0的。在这个地方就是将前节点的状态修改为-1.然后从新执行循环,再判断前一节点的状态=-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;
}
// 挂起当前线程,返回的是当前线程是否中断,一般都是false。
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);// 线程是挂起在这里的。线程重新调起,还是从这里执行。
    return Thread.interrupted();
}

以上就是ReentrantLock中公平锁加锁的代码分析。感谢各位朋友花费时间阅读!希望这篇文档对你有帮助。

参考文档:Javadoop大佬