AbstractQueuedSynchronizer,简称 AQS,是一个用于构建锁和同步器的框架。
JUC 包下常见的锁工具如 ReentrantLock、ReentrantReadWriteLock、Semaphore、CountDownLatch 都是基于 AQS 实现的。
本文将介绍 AQS 的数据结构及独占模式的实现原理。
1. AQS 框架
AQS 所有操作都围绕着同步资源(synchronization state)来开展,解决了资源拜访的互斥和同步问题。
- 反对独占、共享形式来拜访资源。
- 对于无奈获取资源的线程,在同步队列中期待,直到获取资源胜利(或超时、中断)并出队。
- 线程胜利获取资源之后,若指定条件不成立,则开释资源并进入条件队列中期待,直到被唤醒,再转移到同步队列中期待再次获取资源。
AQS框架将剩下的一个问题留给用户:获取、开释资源的具体形式和后果。
这其实是一种典型的模板办法设计模式:父类(AQS框架)定义好骨架和外部操作细节,具体规定由子类去实现。
1.1 继承体系
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 来实现的。
2. 数据结构
2.1 资源定义
锁和资源是同一个概念,是多个线程抢夺的对象。
AQS 应用 state 来示意资源/锁,通过内置的期待队列来实现获取资源/锁的排队工作。
期待队列(wait queue)是严格的 FIFO 队列,是 CLH 锁队列的变种。
因为 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 作为节点。
节点的定义次要蕴含三局部内容:
- 节点的状态:SIGNAL、CANCELLED、CONDITION、PROPAGATE、0。
- 节点的模式:同步队列中的节点具备两种模式,独占(SHARED)、共享(EXCLUSIVE)。
- 节点的指向:同步队列是双向链表(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 肯定不是 CANCELLEDprivate 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();}
- tryAcquire:尝试间接获取资源/锁,如果胜利则间接返回,失败进入下一步;
- addWaiter:获取资源/锁失败后,将以后线程退出同步队列的尾部,并标记为独占模式,返回新入队的节点;
- acquireQueued:使线程在同步队列期待获取资源,始终获取到后才返回,如果在期待过程中被中断过,则返回true,否则返回false。
- 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; // 返回以后节点的上一个节点(旧的尾节点) } } }}
留神:
- 当第一次产生争用时,抢夺锁失败的线程入队,会先结构空节点(dummy node)作为 head/tail 节点进行初始化队列,再从队列尾部入队。
- 入队时,顺次设置 node.prev、tail、pred.next 指针,是非原子操作。
- 设置 prev 之后,若 CAS 设置 tail 失败,阐明其余线程先一步入队了,此时进入下一次循环会修改 prev 的指向。
- 因为入队是非原子操作,并发状况下可能无奈从 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 办法中,线程在自旋中次要进行两个判断:
- 是否获取锁
- 是否进入阻塞
具体代码流程:
- 在同步队列中自旋,若判断前继节点为头节点,则以后节点尝试获取锁。
- 若以后线程获取锁胜利,则将以后节点设为头节点,返回以后线程的中断状态。
- 若以后线程无奈获取锁、获取锁失败,则判断是否进入阻塞。
- 如果无奈进入阻塞,则持续自旋,否则进入阻塞。
- 线程从阻塞中被唤醒后,查看并标记线程的中断状态,从新进入自旋。
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,以确保上一个节点在开释锁之后,可能唤醒以后节点。
分为三种状况:
- 上一个节点状态为 Node.SIGNAL,阐明以后节点已具备被唤醒的条件,能够进入阻塞。
- 上一个节点状态为已勾销,则把以后节点排到未勾销的节点前面,持续自旋不进入阻塞。
- 上一个节点状态为 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 的后继节点:
- 无后继节点,则须要将最初一个无效节点(waitStatus <= 0)设为 tail。
- 存在后继节点,则须要将它挂在最初的无效节点之后,后续由该节点来唤醒后继节点。
- 存在后继节点,且找不到无效的前继节点,则间接把该后继节点唤醒。
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 节点最近的未勾销的节点,对它进行唤醒。
为什么不从以后节点向后遍历无效节点呢?
- 以后节点可能是尾节点,不存在后继节点。
- 入队时先设置 prev 指针,再设置 next 指针(见 AbstractQueuedSynchronizer#enq),是非原子操作,依据 prev 指针往前遍历比拟精确。
3.2 获取锁-acquireInterruptibly
比照 acquire,两者对获取锁过程中产生中断的解决不同。
- acquire 期待锁的过程产生中断,会等到获取锁胜利之后,再解决中断。
- 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;}
- tryRelease:开释锁资源,开释胜利则进入下一步。
- 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 8 源码:AQS 中的独占模式
浏览 JDK 8 源码:AQS 中的共享模式
浏览 JDK 8 源码:AQS 对 Condition 的实现
作者:Sumkor