AbstractQueuedSynchronizer,简称 AQS,是一个用于构建锁和同步器的框架。
JUC 包下常见的锁工具如 ReentrantLock、ReentrantReadWriteLock、Semaphore、CountDownLatch 都是基于 AQS 实现的。
本文将介绍 AQS 的数据结构及独占模式的实现原理。

本文基于 jdk1.8.0_91

1. AQS 框架

AQS 所有操作都围绕着同步资源(synchronization state)来展开,解决了资源访问的互斥和同步问题。

  • 支持独占、共享方式来访问资源。
  • 对于无法获取资源的线程,在同步队列中等待,直到获取资源成功(或超时、中断)并出队。
  • 线程成功获取资源之后,若指定条件不成立,则释放资源并进入条件队列中等待,直到被唤醒,再转移到同步队列中等待再次获取资源。

AQS框架将剩下的一个问题留给用户:获取、释放资源的具体方式和结果。
这其实是一种典型的模板方法设计模式:父类(AQS框架)定义好骨架和内部操作细节,具体规则由子类去实现。

1.1 继承体系

继承体系

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable

AbstractQueuedSynchronizer 继承 AbstractOwnableSynchronizer,后者具有属性 exclusiveOwnerThread,用于记录独占模式下获得锁的线程。

public abstract class AbstractOwnableSynchronizer implements java.io.Serializable {
    /**
     * The current owner of exclusive mode synchronization.
     */
    private transient Thread exclusiveOwnerThread;
}    

AbstractQueuedSynchronizer 具有 ConditionObject 和 Node 两个内部类。

  • ConditionObject 是对 Condition 接口的实现,可以与 Lock 配合使用。
  • Node 是 AQS 中同步队列、条件队列的节点。

1.2 模板方法

AQS 定义了一系列模板方法如下:

// 独占获取(资源数)
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

// 独占释放(资源数)
protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}

// 共享获取(资源数)
protected int tryAcquireShared(int arg) {
    throw new UnsupportedOperationException();
}

// 共享获取(资源数)
protected boolean tryReleaseShared(int arg) {
    throw new UnsupportedOperationException();
}

// 是否排它状态
protected boolean isHeldExclusively() {
    throw new UnsupportedOperationException();
}    

Java 中常用的锁工具都是基于 AQS 来实现的。

AQS工具

2. 数据结构

2.1 资源定义

锁和资源是同一个概念,是多个线程争夺的对象。
AQS 使用 state 来表示资源/锁,通过内置的等待队列来完成获取资源/锁的排队工作。
等待队列(wait queue)是严格的 FIFO 队列,是 CLH 锁队列的变种。

state

由于 state 是共享的,使用 volatile 来保证其可见性,并提供了getState/setState/compareAndSetState三个方法来操作 state。

/**
 * The synchronization state.
 */                          
private volatile int state;// 资源/锁

/**
 * Returns the current value of synchronization state.
 * This operation has memory semantics of a {@code volatile} read.
 * @return current state value
 */
protected final int getState() {
    return state;
}

/**
 * Sets the value of synchronization state.
 * This operation has memory semantics of a {@code volatile} write.
 * @param newState the new state value
 */
protected final void setState(int newState) {
    state = newState;
}

/**
 * Atomically sets synchronization state to the given updated
 * value if the current state value equals the expected value.
 * This operation has memory semantics of a {@code volatile} read
 * and write.
 *
 * @param expect the expected value
 * @param update the new value
 * @return {@code true} if successful. False return indicates that the actual
 *         value was not equal to the expected value.
 */
protected final boolean compareAndSetState(int expect, int update) { // 原子操作
    // See below for intrinsics setup to support this
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

2.2 节点定义

AQS 的内部实现了两个队列:同步队列和条件队列。这两种队列都使用了 Node 作为节点。

节点的定义主要包含三部分内容:

  1. 节点的状态:SIGNAL、CANCELLED、CONDITION、PROPAGATE、0。
  2. 节点的模式:同步队列中的节点具有两种模式,独占(SHARED)、共享(EXCLUSIVE)。
  3. 节点的指向:同步队列是双向链表(prev/next),条件队列是单向链表(nextWaiter)。

节点的状态

  • CANCELLED:值为 1,表示当前节点由于超时或中断被取消。
  • SIGNAL: 值为 -1,唤醒信号,表示当前节点的后继节点正在等待获取锁。该状态下的节点在 release 或 cancel 时需要执行 unpark 来唤醒后继节点。
  • CONDITION:值为 -2,表示当前节点为条件队列节点,同步队列的节点不会有这个状态。当节点从条件队列转移到同步队列时,状态会初始化为 0。
  • PROPAGATE:值为 -3,只有共享模式下,同步队列的头节点才会设置为该状态(见 doReleaseShared),表示后继节点可以发起获取共享资源的操作。
  • 0:初始状态,表示当前节点在同步队列中,等待获取锁。

java.util.concurrent.locks.AbstractQueuedSynchronizer.Node

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;

    // 等待状态:SIGNAL、CANCELLED、CONDITION、PROPAGATE、0
    volatile int waitStatus;

    // 指向同步队列中的上一个节点
    volatile Node prev;

    // 指向同步队列中的下一个节点
    volatile Node next;

    volatile Thread thread;

    // 在同步队列中,nextWaiter用于标记节点的模式:独占、共享
    // 在条件队列中,nextWaiter指向条件队列中的下一个节点
    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;
    }
}

2.3 同步队列

同步队列
同步队列是等待获取锁的队列,是一个双向链表(prev/next),使用 head/tail 执行队列的首尾节点。

java.util.concurrent.locks.AbstractQueuedSynchronizer

/**
 * 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.
 */
// 等待队列的头节点,懒初始化。 
// 注意,如果头节点存在,那么它的 waitStatus 一定不是 CANCELLED
private transient volatile Node head;

/**
 * Tail of the wait queue, lazily initialized.  Modified only via  
 * method enq to add new wait node.                                
 */
// 等待队列的尾节点,懒初始化。 
// 只能通过 enq 方法给等待队列添加新的节点。
private transient volatile Node tail;

/**
 * The synchronization state.
 */
private volatile int state;

在线程尝试获取资源失败后,会进入同步队列队尾,给前继节点设置一个唤醒信号后,通过 LockSupport.park(this) 让自身进入等待状态,直到被前继节点唤醒。

当线程在同步队列中等待,获取资源成功后,通过执行 setHead(node) 将自身设为头节点。
同步队列的头节点是一个 dummy node,它的 thread 为空(某些情况下可以看做是代表了当前持有锁的线程)。

/**
 * Sets head of queue to be node, thus dequeuing. Called only by
 * acquire methods.  Also nulls out unused fields for sake of GC
 * and to suppress unnecessary signals and traversals.
 *
 * @param node the node
 */
private void setHead(Node node) {
    head = node;
    node.thread = null;
    node.prev = null;
}

AQS 不会在初始化队列的时候构建空的头节点(dummy node),而是在第一次发生争用时构造:
第一个线程获取锁,第二个线程获取锁失败入队,此时才会初始化队列,构造空节点并将 head/tail 指向该空节点。
具体见 AbstractQueuedSynchronizer#enq。

2.4 条件队列

条件队列

条件队列是等待条件成立的队列,是一个单向链表(nextWaiter),使用 firstWaiter/lastWaiter 指向队列的首尾节点。

java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject

/** First node of condition queue. */
private transient Node firstWaiter; // 条件队列的头节点
/** Last node of condition queue. */
private transient Node lastWaiter;  // 条件队列的尾节点

当线程获取锁成功之后,执行 Conition.await(),释放锁并进入条件队列中等待,直到其他线程执行 Conition.signal 唤醒当前线程。

当前线程被唤醒后,从条件队列转移到同步队列,重新等待获取锁。

3. 独占模式

独占模式下,只要有一个线程占有锁,其他线程试图获取该锁将无法取得成功。

3.1 获取锁-acquire

独占模式下获取锁/资源,无视中断,Lock#lock的内部实现

java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire

public final void acquire(int arg) { 
    if (!tryAcquire(arg) &&                            
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 
        selfInterrupt();
}
  1. tryAcquire:尝试直接获取资源/锁,如果成功则直接返回,失败进入下一步;
  2. addWaiter:获取资源/锁失败后,将当前线程加入同步队列的尾部,并标记为独占模式,返回新入队的节点;
  3. acquireQueued:使线程在同步队列等待获取资源,一直获取到后才返回,如果在等待过程中被中断过,则返回true,否则返回false。
  4. selfInterrupt:如果线程在等待过程中被中断过,在获取资源成功之后,把中断状态补上。

3.1.1 tryAcquire

尝试获取资源,成功返回true。具体资源获取方式交由自定义同步器实现。

java.util.concurrent.locks.AbstractQueuedSynchronizer#tryAcquire

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

3.1.2 addWaiter

获取资源/锁失败后,将当前线程封装为新的节点,设置节点的模式(独占、共享),加入同步队列的尾部,返回该新节点。

java.util.concurrent.locks.AbstractQueuedSynchronizer#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); // tail为空,入队
    return node; // 返回当前的新节点
}
enq

从同步队列的尾部入队,如果队列不存在则进行初始化。

java.util.concurrent.locks.AbstractQueuedSynchronizer#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)) { // 队列不为空,则当前节点作为新的tail // CAS失败,可能会出现尾分叉的现象,由下一次循环消除分叉
                t.next = node;                // 由于不是原子操作,入队操作先设置prev指针,再设置next指针,会导致并发情况下无法通过next遍历到尾节点
                return t;                     // 返回当前节点的上一个节点(旧的尾节点)
            }
        }
    }
}

注意:

  1. 当第一次发生争用时,争夺锁失败的线程入队,会先构造空节点(dummy node)作为 head/tail 节点进行初始化队列,再从队列尾部入队。
  2. 入队时,依次设置 node.prev、tail、pred.next 指针,是非原子操作。
  3. 设置 prev 之后,若 CAS 设置 tail 失败,说明其他线程先一步入队了,此时进入下一次循环会修正 prev 的指向。
  4. 由于入队是非原子操作,并发情况下可能无法从 head 开始通过 next 遍历到尾节点 tail,但是从尾节点 tail 开始通过 prev 向前遍历可以访问到完整的队列。

3.1.3 acquireQueued

在同步队列自旋、等待获取资源直到成功,返回等待期间的中断状态。

java.util.concurrent.locks.AbstractQueuedSynchronizer#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)) { // 上一个节点如果是头结点,说明当前节点的线程可以尝试获取锁资源
                // 获取锁成功,当前节点作为新的头节点,并且清理掉当前节点中的线程信息(也就是说头节点是个dummy node)
                // 这里不会发生争用,不需要CAS
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // 上一个节点不是头节点,或者当前节点的线程获取锁失败,需要判断是否进入阻塞:
            // 1. 不能进入阻塞,则重试获取锁。2. 进入阻塞
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt()) // 阻塞当前线程,当从阻塞中被唤醒时,检测当前线程是否已中断,并清除中断状态。接着继续重试获取锁。
                interrupted = true;      // 标记当前线程已中断(如果线程在阻塞时被中断唤醒,会重试获取锁直到成功之后,再响应中断)
        }
    } finally {
        if (failed)              // 自旋获取锁和阻塞过程中发生异常
            cancelAcquire(node); // 取消获取锁
    }
}

在 acquireQueued 方法中,线程在自旋中主要进行两个判断:

  1. 能否获取锁
  2. 能否进入阻塞

具体代码流程:

  1. 在同步队列中自旋,若判断前继节点为头节点,则当前节点尝试获取锁。
  2. 若当前线程获取锁成功,则将当前节点设为头节点,返回当前线程的中断状态。
  3. 若当前线程无法获取锁、获取锁失败,则判断是否进入阻塞。
  4. 如果无法进入阻塞,则继续自旋,否则进入阻塞。
  5. 线程从阻塞中被唤醒后,检查并标记线程的中断状态,重新进入自旋。
shouldParkAfterFailedAcquire

当前节点获取锁失败之后,通过校验上一个节点的等待状态,判断当前节点能否进入阻塞。
返回 true,可进入阻塞;返回 false,不可进入阻塞,需重试获取锁。

java.util.concurrent.locks.AbstractQueuedSynchronizer#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 // 上一个节点状态大于 0,说明是已取消状态 CANCELLED,不会通知当前节点
         * indicate retry.                                       // 则一直往前找到一个等待状态的节点,并排在它的后边
         */                                                      // 当前节点不能进入阻塞,需重试获取锁
        do {
            node.prev = pred = pred.prev; // pred = pred.prev; node.prev = pred; // 跳过上一个节点,直到找到 waitStatus > 0 的节点
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        /*
         * waitStatus must be 0 or PROPAGATE.  Indicate that we    // 上一个节点状态等于 0 或 PROPAGATE,说明正在等待获取锁/资源
         * need a signal, but don't park yet.  Caller will need to // 此时需要给上一个节点设置唤醒信号SIGNAL,但不直接阻塞
         * retry to make sure it cannot acquire before parking.    // 因为在阻塞前调用者需要重试来确认它确实不能获取资源
         */
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL); // 通过 CAS 将上一个节点的状态改为 SIGNAL
    }
    return false;
}

当前节点能够进入阻塞的条件是:具有其他线程来唤醒它。
通过设置上一个节点状态为 SIGNAL,以确保上一个节点在释放锁之后,能够唤醒当前节点。

分为三种情况:

  1. 上一个节点状态为 Node.SIGNAL,说明当前节点已具备被唤醒的条件,可以进入阻塞。
  2. 上一个节点状态为已取消,则把当前节点排到未取消的节点后面,继续自旋不进入阻塞。
  3. 上一个节点状态为 0 或 PROPAGATE,说明正在等待获取锁,则当前节点将上一个节点设为 SIGNAL,继续自旋不进入阻塞。
parkAndCheckInterrupt

进入阻塞,阻塞结束后,检查中断状态。

java.util.concurrent.locks.AbstractQueuedSynchronizer#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();
}
cancelAcquire

线程在 acquireQueued 中自旋尝试获取锁的过程中,如果发生异常,会在 finally 代码块中执行 cancelAcquire,终止获取锁。

/**
 * Cancels an ongoing attempt to acquire.
 *
 * @param node the node
 */
private void cancelAcquire(Node node) { // 取消获取锁
    // Ignore if node doesn't exist
    if (node == null)
        return;

    node.thread = null;

    // Skip cancelled predecessors // 跳过已取消的前继节点,为当前节点找出一个有效的前继节点
    Node pred = node.prev;
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;

    // predNext is the apparent node to unsplice. CASes below will
    // fail if not, in which case, we lost race vs another cancel
    // or signal, so no further action is necessary.
    Node predNext = pred.next;

    // Can use unconditional write instead of CAS here.        // 写操作具有可见性(volatile),因此这里无需使用 CAS
    // After this atomic step, other Nodes can skip past us.   // 把当前节点设为已取消之后,其他节点寻找有效前继节点时会跳过当前节点
    // Before, we are free of interference from other threads.
    node.waitStatus = Node.CANCELLED;

    // If we are the tail, remove ourselves.
    if (node == tail && compareAndSetTail(node, pred)) { // 如果是尾节点,则出队
        compareAndSetNext(pred, predNext, null);
    } else {                                                      // 进入这里,说明不是尾节点,或者是尾节点但出队失败,需要处理后继节点
        // If successor needs signal, try to set pred's next-link // 如果后继节点需要得到通知,则尝试给它找一个新的前继节点
        // so it will get one. Otherwise wake it up to propagate. // 否则把后继节点唤醒
        int ws;
        if (pred != head &&                                       // 前继节点不是头节点
            ((ws = pred.waitStatus) == Node.SIGNAL ||             // 前继节点的状态为SIGNAL 或者 前继节点的状态为未取消且尝试设置为SIGNAL成功
             (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
            pred.thread != null) {
            Node next = node.next;
            if (next != null && next.waitStatus <= 0)    // 后继节点存在且未取消
                compareAndSetNext(pred, predNext, next); // 给后继节点设置一个新的前继节点(即前面找的有效节点),当前节点出队
        } else {
            unparkSuccessor(node); // 如果存在后继节点,这里说明无法给后继节点找到新的前继节点(可能前继节点是head,或者前继节点失效了),直接唤醒该后继节点
        }

        node.next = node; // help GC
    }
}

节点 node 取消获取锁,说明当前节点 node 状态变为已取消,成为一个无效节点。

需要考虑如何处理节点 node 的后继节点:

  1. 无后继节点,则需要将最后一个有效节点(waitStatus <= 0)设为 tail。
  2. 存在后继节点,则需要将它挂在最后的有效节点之后,后续由该节点来唤醒后继节点。
  3. 存在后继节点,且找不到有效的前继节点,则直接把该后继节点唤醒。
unparkSuccessor

唤醒当前节点的后继节点。

java.util.concurrent.locks.AbstractQueuedSynchronizer#unparkSuccessor

/**
 * Wakes up node's successor, if one exists.
 *
 * @param node the node
 */
// 唤醒当前节点的后继节点 
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; // 如果当前节点的状态为已取消,则不变;如果小于0(有可能后继节点需要当前节点来唤醒),则清零。
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0); // CAS失败也无所谓(说明后继节点的线程先一步修改了当前节点的状态),因为接下来会手动唤醒后继节点

    Node s = node.next;
    if (s == null || s.waitStatus > 0) { // 后继节点为空,或已取消,则从tail开始向前遍历有效节点
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t; // // 注意! 这里找到了之后并没有return, 而是继续向前找
    }
    if (s != null)
        LockSupport.unpark(s.thread); // 唤醒后继节点(或者是队列中距离head节点最近的有效节点)的线程
}

通常情况下, 要唤醒的节点就是自己的后继节点。如果后继节点存在且也在等待锁, 那就直接唤醒它。
但是有可能存在 后继节点取消等待锁 的情况,此时从尾节点开始向前找起, 直到找到距离 head 节点最近的未取消的节点,对它进行唤醒。

为什么不从当前节点向后遍历有效节点呢?

  1. 当前节点可能是尾节点,不存在后继节点。
  2. 入队时先设置 prev 指针,再设置 next 指针(见 AbstractQueuedSynchronizer#enq),是非原子操作,根据 prev 指针往前遍历比较准确。

3.2 获取锁-acquireInterruptibly

对比 acquire,两者对获取锁过程中发生中断的处理不同。

  1. acquire 等待锁的过程发生中断,会等到获取锁成功之后,再处理中断。
  2. acquireInterruptibly 等待锁的过程发生中断,会立即抛出 InterruptedException,不再等待获取锁。

java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireInterruptibly

public final void acquireInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (!tryAcquire(arg))
        doAcquireInterruptibly(arg);
}

java.util.concurrent.locks.AbstractQueuedSynchronizer#doAcquireInterruptibly

/**
 * Acquires in exclusive interruptible mode.
 * @param arg the acquire argument
 */
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);
    }
}

3.3 释放锁-release

独占模式下释放锁/资源,是 Lock#unlock 的内部实现。

java.util.concurrent.locks.AbstractQueuedSynchronizer#release

public final boolean release(int arg) {
    if (tryRelease(arg)) { // 释放锁资源
        Node h = head;
        if (h != null && h.waitStatus != 0) // head.waitStatus == 0,说明head节点后没有需要唤醒的节点
            unparkSuccessor(h); // 唤醒head的后继节点
        return true;
    }
    return false;
}
  1. tryRelease:释放锁资源,释放成功则进入下一步。
  2. unparkSuccessor:如果同步队列的头节点存在且满足 waitStatus != 0,则唤醒后继节点。

java.util.concurrent.locks.AbstractQueuedSynchronizer#tryRelease

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

头节点 h 的状态:

  • h.waitStatus == 0,同步队列中的节点初始状态为 0,说明没有需要唤醒的后继节点。
  • h.waitStatus < 0,独占模式下,说明是 SIGNAL 状态,此时具有后继节点等待唤醒,见 AbstractQueuedSynchronizer#shouldParkAfterFailedAcquire。
  • h.waitStatus > 0,说明是 CANCELLED 状态,头节点是由取消获取锁的节点而来的,保险起见,检查有没有需要唤醒的后继节点。

相关阅读:
阅读 JDK 源码:AQS 中的独占模式
阅读 JDK 源码:AQS 中的共享模式
阅读 JDK 源码:AQS 对 Condition 的实现

作者:Sumkor


Sumkor
148 声望1.3k 粉丝

会写点代码