1、简介
在JDK 1.6之前,synchronized这个重量级锁其性能一直都是较为低下,虽然在1.6后,进行大量的锁优化策略,但是与Lock相比synchronized还是存在一些缺陷的:虽然synchronized提供了便捷性的隐式获取锁释放锁机制(基于JVM机制),但是它却缺少了获取锁与释放锁的可操作性,可中断、超时获取锁,且它为独占式在高并发场景下性能大打折扣。 在介绍Lock之前,我们需要先熟悉一个非常重要的组件,该组件就是AQS。
AQS,AbstractQueuedSynchronizer,即队列同步器。它是构建锁或者其他同步组件的基础框架。它是JUC并发包中的核心基础组件。 AQS框架是J.U.C中实现锁及同步机制的基础,其底层是通过调用LockSupport.unpark()和 LockSupport.park()实现线程的阻塞和唤醒。
AQS的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态。 AQS使用一个int类型的成员变量state来表示同步状态,当state>0时表示已经获取了锁,当state = 0时表示释放了锁。其中state是用volatile修饰的,保证线程之间的可见性,队列的入队和出对操作都是无锁操作,基于自旋锁和CAS实现;它提供了三个方法(getState()、setState(int newState)、compareAndSetState(int expect,int update))来对同步状态state进行操作,当然AQS可以确保对state的操作是安全的。
AQS通过内置的FIFO同步队列来完成资源获取线程的排队工作,如果当前线程获取同步状态失败(锁)时,AQS则会将当前线程以及等待状态等信息构造成一个节点(Node)并将其加入同步队列,同时会阻塞当前线程,当同步状态释放时,则会把节点中的线程唤醒,使其再次尝试获取同步状态。 AQS分为两种模式:独占模式和共享模式ReentrantLock是基于独占模式模式实现的,
CountDownLatch、CyclicBarrier等是基于共享模式。
2、原理分析
在 AQS 内部,通过维护一个 CLH同步队列是一个FIFO双向队列,AQS依赖它来完成同步状态的管理,当前线程如果获取同步状态失败时,AQS则会将当前线程已经等待状态等信息构造成一个节点(Node)并将其加入到CLH同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点唤醒(公平锁),使其再次尝试获取同步状态。 在CLH同步队列中,一个节点表示一个线程,它保存着线程的引用(thread)、状态(waitStatus)、前驱节点(prev)、后继节点(next)
- 队列结点结构如下:
static final class Node {
// 共享模式
static final Node SHARED = new Node();
// 独占模式
static final Node EXCLUSIVE = null;
// 因为超时或者中断,节点会被设置为取消状态,被取消的节点时不会参与到竞争中的,
static final int CANCELLED = 1;
// 后继节点的线程处于等待状态,而当前节点的线程如果释放了同步状态或者被取消,会通知后继节点运行
static final int SIGNAL = -1;
// 节点在等待队列中,节点线程等待在Condition上
static final int CONDITION = -2;
// 表示后继结点可以直接获取锁
static final int PROPAGATE = -3;
// 等待状态,waitStatus = 0
volatile int waitStatus;
// 前驱节点
volatile Node prev;
// 后置结点
volatile Node next;
// 获取同步状态的线程
volatile Thread thread;
// 下一个等待节点,用在 ConditionObject 中
Node nextWaiter;
// 结点状态是否是共享模式
final boolean isShared() {
return nextWaiter == SHARED;
}
// 返回当前结点的前继结点
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() {
}
// addWaiter 方法会调用该构造方法
Node(Thread thread, Node mode) {
this.nextWaiter = mode;
this.thread = thread;
}
// Condition 中会用到此构造方法
Node(Thread thread, int waitStatus) {
this.waitStatus = waitStatus;
this.thread = thread;
}
}
同步器包含两个节点类型的应用,一个指向头节点,一个指向尾节点,未获取到锁的线程会创建节点线程安全(compareAndSetTail)的加入队列尾部。同步队列遵循FIFO,首节点是获取同步状态成功的节点。
3、重要方法介绍
本节将介绍三组重要的方法,通过使用这三组方法即可实现一个同步组件。
3.1 用于访问/设置同步状态
方法 | 说明 |
---|---|
int getState() | 获取同步状态 |
void setState() | 设置同步状态 |
boolean compareAndSetState(int expect, int update) | 通过 CAS 设置同步状态 |
3.2 需要同步组件覆写
方法 | 说明 |
---|---|
boolean tryAcquire(int arg) | 独占式获取同步状态,成功则返回true,失败则返回false |
boolean tryRelease(int arg) | 独占式释放同步状态,成功则返回true,失败则返回false |
int tryAcquireShared(int arg) | 共享式获取同步状态,负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功 |
boolean tryReleaseShared(int arg) | 共享式私房同步状态,成功则返回true,失败则返回false |
boolean isHeldExclusively() | 检测当前线程是否获取独占锁,只有用到condition才需要去实现它 |
3.3 模板方法,同步组件可直接调用
同步器的设计是基于模板方法模式的,如果需要自定义同步器一般的方式是这样:
- 使用者继承AbstractQueuedSynchronizer并重写指定的方法。(这些重写方法很简单,无非是对于共享资源state的获取和释放)
- 将AQS组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。
方法 | 说明 |
---|---|
void acquire(int arg) | 独占式获取同步状态,该方法将会调用 tryAcquire 尝试获取同步状态。获取成功则返回,获取失败,线程进入同步队列等待。 |
void acquireInterruptibly(int arg) | 响应中断版的 acquire |
boolean tryAcquireNanos(int arg,long nanos) | 超时+响应中断版的 acquire |
void acquireShared(int arg) | 共享式获取同步状态,同一时刻可能会有多个线程获得同步状态。比如读写锁的读锁就是就是调用这个方法获取同步状态的。 |
void acquireSharedInterruptibly(int arg) | 响应中断版的 acquireShared |
boolean tryAcquireSharedNanos(int arg,long nanos) | 超时+响应中断版的 acquireShared |
boolean release(int arg) | 独占式释放同步状态 |
boolean releaseShared(int arg) | 共享式释放同步状态 |
4、源码分析-CLH队列
4.1 AQS类属性
// 等待队列头结点
private transient volatile Node head;
// 等待队列尾结点
private transient volatile Node tail;
// 表示同步状态,volatile保证线程可见性,使用CAS对该同步状态修改
private volatile int state;
4.2 加入同步队列
// 先通过快速尝试设置尾节点,如果失败,则调用enq(Node node)方法设置尾节点
private Node addWaiter(Node mode) {
// mode为当前节点的nextWaiter
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
// cas加入到尾部
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
// 自旋方式添加当前节点,直到更新成功
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) {
// 当前队列为空
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 存在前置节点
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
两个方法都是通过一个CAS方法compareAndSetTail(Node expect, Node update)来设置尾节点,该方法可以确保节点是线程安全添加的。在enq(Node node)方法中,AQS通过“死循环”的方式来保证节点可以正确添加,只有成功添加后,当前线程才会从该方法返回,否则会一直执行下去。
未获取到锁的线程将创建一个节点,设置到尾节点。如下图所示:
4.3 出队列
首节点的线程释放同步状态后,将会唤醒它的后继节点(next),而后继节点将会在获取同步状态成功时将自己设置为首节点,这个过程非常简单,head执行该节点并断开原首节点的next和当前节点的prev即可,注意在这个过程是不需要使用CAS来保证的,因为只有一个线程能够成功获取到同步状态。
首节点的线程在释放锁时,将会唤醒后继节点。而后继节点将会在获取锁成功时将自己设置为首节点。如下图所示:
5、源码分析-独占与共享锁
独占 | 共享 |
---|---|
Exclusive() | Share |
同一时刻仅有一个线程持有同步状态,如ReentrantLock。又可分为公平锁和 | 多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatch、 CyclicBarrier |
非公平锁:公平锁:按照线程在队列中的排队顺序,先到者先拿到锁;非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的 | 可以多个线程同时获取到锁 |
5.1 独占模式
5.1.1 获取同步状态
acquire(int arg)方法为AQS提供的模板方法,该方法为独占式获取同步状态,但是该方法对中断不敏感,也就是说由于线程获取同步状态失败加入到CLH同步队列中,后续对线程进行中断操作时,线程不会从同步队列中移除。
public final void acquire(int arg) {
// tryAcquire使用cas更新状态state,设置当前线程占用,在子类中实现
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// 产生一个中断
selfInterrupt();
}
// 向同步队列尾部添加一个节点,详细可见“加入CLH队列”
private Node addWaiter(Node mode) {
// 以独占模式创建节点,mode=null即nextWaiter=null
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
// 刚加加入队列尾部节点以循环尝试获取同步状态,
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)) {
// 将给定结点设置为head结点
setHead(node);
p.next = null;
// 获取成功状态
failed = false;
//返回中断的状态
return interrupted;
}
// 请求失败处理策略
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)
// 前驱节点等待状态为 SIGNAL,表示当前线程应该被阻塞。
return true;
if (ws > 0) {
// 前驱节点等待状态为 CANCELLED,移除等待状态为 CANCELLED 的节点
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 等待状态为 0 或 PROPAGATE,
//这样的话前继结点就不会去唤醒当前结点了,设置前驱节点等待状态为 SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
// 挂起当前线程
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
// 取消获取同步状态
private void cancelAcquire(Node node) {
if (node == null)
return;
node.thread = null;
// 前驱节点等待状态为 CANCELLED,则向前遍历并移除其他为该状态的节点
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// 记录 pred 的后继节点,后面会用到
Node predNext = pred.next;
// 将当前节点等待状态设为 CANCELLED
node.waitStatus = Node.CANCELLED;
// 如果当前节点是尾节点,则通过 CAS 设置前驱节点 prev 为尾节点。设置成功后,
// 再利用CAS将prev 的 next 引用置空,断开与后继节点的联系,完成清理工作
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
// 根据条件判断是唤醒后继节点,还是将前驱节点和后继节点连接到一起
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.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);
}
node.next = node;
}
}
private void unparkSuccessor(Node node) {
/*
* 通过 CAS 将等待状态设为 0,让后继节点线程多一次
* 尝试获取同步状态的机会
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
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)
// 唤醒 node 的后继节点线程
LockSupport.unpark(s.thread);
}
独占式锁获取流程:
5.1.2 独占式获取响应中断
AQS提供了acquire(int arg)方法以供独占式获取同步状态,但是该方法对中断不响应,对线程进行中断操作后,该线程会依然位于CLH同步队列中等待着获取同步状态。为了响应中断,AQS提供了acquireInterruptibly(int arg)方法,该方法在等待获取同步状态时,如果当前线程被中断了,会立刻响应中断抛出异常InterruptedException。
public final void acquireInterruptibly(int arg)
throws InterruptedException {
// 校验该线程是否已经中断了,如果是则抛出异常
if (Thread.interrupted())
throw new InterruptedException();
// 执行tryAcquire(int arg)方法获取同步状态
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
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);
}
}
5.1.3 独占式超时获取
它除了响应中断外,还有超时控制。即如果当前线程没有在指定时间内获取同步状态,则会返回false,否则返回true
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
}
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
// 超时时间
final long deadline = System.nanoTime() + nanosTimeout;
// 新增Node节点
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 true;
}
// 获取失败,做超时、中断判断
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
return false;
// 如果没有超时,则等待nanosTimeout纳秒
// 注:该线程会直接从LockSupport.parkNanos中返回
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
5.1.4 独占式同步状态释放
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();
}
// 释放成功后,会调用unparkSuccessor(Node node)方法唤醒后继节点
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
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);
}
5.2 共享模式
共享式与独占式的最主要区别在于同一时刻独占式只能有一个线程获取同步状态,而共享式在同一时刻可以有多个线程获取同步状态。例如读操作可以有多个线程同时进行,而写操作同一时刻只能有一个线程进行写操作,其他操作都会被阻塞。
5.2.1 共享式同步状态获取
public final void acquireShared(int arg) {
// tryAcquireShared在子类实现
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
private void doAcquireShared(int arg) {
// 共享式节点,nextWaiter = Node.SHARED
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;
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head;
// 设置当前节点为头节点
setHead(node);
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
共享锁获取流程:
5.2.2 共享式同步状态释放
可能会存在多个线程同时进行释放同步状态资源,所以需要确保同步状态安全地成功释放,一般都是通过CAS和循环来完成的
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
if (h == head)
break;
}
}
5.3 PROPAGATE 状态作用
问题一:PROPAGATE 状态用在哪里,以及怎样向后传播唤醒动作的?
答:PROPAGATE 状态用在 setHeadAndPropagate。当头节点状态被设为 PROPAGATE 后,后继节点成为新的头结点后。若 propagate > 0 条件不成立,则根据条件h.waitStatus < 0成立与否,来决定是否唤醒后继节点,即向后传播唤醒动作。
问题二:引入 PROPAGATE 状态是为了解决什么问题?
答:引入 PROPAGATE 状态是为了解决并发释放信号量所导致部分请求信号量的线程无法被唤醒的问题。
5.4 LockSupport
每个使用LockSupport的线程都会与一个许可关联,如果该许可可用,并且可在进程中使用,则调用park()将会立即返回,否则可能阻塞。如果许可尚不可用,则可以调用 unpark 使其可用。但是注意许可不可重入,也就是说只能调用一次park()方法,否则会一直阻塞。 LockSupport定义了一系列以park开头的方法来阻塞当前线程,unpark(Thread thread)方法来唤醒一个被阻塞的线程。
6、总结
6.1 CLH队列
AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队
列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资
源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。
6.2 CLH锁
6.2.1 自旋锁和互斥锁
由于CLH锁是一种自旋锁
自旋锁说白了也是一种互斥锁,只不过没有抢到锁的线程会一直自旋等待锁的释放,处于
busy-waiting的状态,此时等待锁的线程不会进入休眠状态,而是一直忙等待浪费CPU周
期。因此自旋锁适用于锁占用时间短的场合。
互斥锁说的是传统意义的互斥锁,就是多个线程并发竞争锁的时候,没有抢到锁的线程会
进入休眠状态即sleep-waiting当锁被释放的时候,处于休眠状态的一个线程会再次获取到锁。
缺点就是这一些列过程需要线程切换,需要执行很多CPU指令,同样需要时间。
如果CPU执行线程切换的时间比锁占用的时间还长,那么可能还不如使用自旋锁。
因此互斥锁适用于锁占用时间长的场合。
6.2.2 CLH锁原理
-
首先有一个尾节点指针,通过这个尾结点指针来构建等待线程的逻辑队列,因此能确保线程线
程先到先服务的公平性,因此尾指针可以说是构建逻辑队列的桥梁;此外这个尾节点指针是原
子引用类型,避免了多线程并发操作的线程安全性问题; -
通过等待锁的每个线程在自己的某个变量上自旋等待,这个变量将由前一个线程写入。由于某
个线程获取锁操作时总是通过尾节点指针获取到前一线程写入的变量,而尾节点指针又是原子
引用类型,因此确保了这个变量获取出来总是线程安全的。 -
ReentrantLock 内部自定义了同步器 Sync (Sync 既实现了 AQS, 又实现了 AOS,而AOS 提供了一种互斥锁持有的方式),其实就是加锁的时候通过CAS算法,将线程对象放到一个双向链表中,每次获取锁的时候,看下当前维护的那个线程ID和当前请求的线程 ID 是否一样,一样就可重入了。
6.3 AQS优点
特性 | 描述 | API |
---|---|---|
能响应中断 | 如果不能自己释放,那可以响应中断也是很好的。Java多线程中断机制 专门描述了中断过程,目的是通过中断信号来跳出某种状态,比如阻塞 | lockInterruptbly() |
非阻塞式的获取锁 | 尝试获取,获取不到不会阻塞,直接返回 | tryLock() |
支持超时 | 给定一个时间限制,如果一段时间内没获取到,不是进入阻塞状态,同样直接返回 | tryLock(long time, timeUnit) |
6.4 AQS核心
- AQS 在内部定义了一个 volatile int state 变量,表示同步状态:当线 程调用 lock 方法
时 ,如果 state=0,说明没有任何线程占有共享资源 的锁,可以获得锁并将 state=1;
如果 state=1,则说明有线程目前正在 使用共享变量,其他线程必须加入同步队列进行等待。 - AQS 通过 Node 内部类构成的一个双向链表结构的同步队列,来完成线程获取锁的排
队工作,当有线程获取锁失败后,就被添加到队列。Node 类是对要访问同步代码
的线程的封装,包含了线程本身及其状态叫waitStatus(有五种不同取值,分别表示是
否被阻塞,是否等待唤醒, 是否已经被取消等),每个Node结点关联其prev结点和
next 结点,方便线程释放锁后快速唤醒下一个在等待的线程,是一个 FIFO 的过 程。
Node 类有两个常量,SHARED 和 EXCLUSIVE,分别代表共享模式和独 占模式。所
谓共享模式是一个锁允许多条线程同时操作(信号量Semaphore 就是基于 AQS 的共享
模式实现的),独占模式是同一个时 间段只能有一个线程对共享资源进行操作,多余的请求线程需要排队等待 ( 如 ReentranLock) 。 - AQS 通过内部类 ConditionObject 构建等待队列(可有多个),当Condition
调用 wait()方法后,线程将会加入等待队列中,而当Condition 调用 signal()
方法后,线程将从等待队列转移动同步队列中进行锁竞争。 - AQS 和 Condition 各自维护了不同的队列,在使用 Lock 和Condition 的时候,
其实就是两个队列的互相移动。