ReentrantLock
ReentrantLock是什么?
定义
- ReentrantLock是一个基于AQS来实现的可重入的互斥锁,它是一个互斥锁,并且支持可重入的。
- ReentrantLock实现了Lock接口,实现了锁的语义。
- ReentrantLock提供了两种锁模式,公平锁和非公平锁,默认是非公平锁。
- ReentrantLock基于AQS之上的Condition机制,实现了多线程通过Condition进行睡眠、唤醒来控制线程行为
ReentrantLock锁demo样例
public class ReentrantLockDemo {
// 获取一个Lock接口的实例,由ReentrantLock实现的
public static Lock lock = new ReentrantLock();
// 定义一个共享变量
public static int value = 0;
public static void add() {
try {
// 执行value++操作之前先获取锁
lock.lock();
value++;
} finally {
// 执行完操作之后释放锁
lock.unlock();
}
}
public static class AddThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
add();
}
}
}
public static void main(String[] args) throws InterruptedException {
// 这里同时开启两个线程执行,验证ReentrantLock是否能保证线程安全
// 也就是ReentrantLock是否能实现锁的作用
AddThread addThread1 = new AddThread();
AddThread addThread2 = new AddThread();
addThread1.start();
addThread2.start();
// 这里主线程调用addThread1.join、addThread2.join的方法
// 意思就等待这两个线程执行完成之后才能继续执行
addThread1.join();
addThread2.join();
System.out.println("value 的值是:" + value);
}
}
这里得到的结果是20000,跟预期值20000完全一致,是并发安全的。
ReentrantLock锁机制源码剖析
ReentrantLock,它内部有一个Sync的内部类,这个Sync继承了AQS,同时Sync又有两个子类,分别是NonfairSync非公平锁、FairSync公平锁。
然后ReentrantLock只是基于已有的公平锁和非公平所同步工具类之上再简单的封装了一层而已。大概的结构图是这样的:
公平锁FairSync和非公平锁NonfairSync都是实现了tryAcquire和tryRelease方法,我们首先来看下FairSync公平锁的内部源码:
公平锁获取锁内部源码解析
如下代码所示,公平锁同步器重写了AQS的tryAcquire方法:
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
// 这里的acquire方法其实就是AQS的acquire方法
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 判断资源是否有人加锁,c == 0 没人加锁,c > 0 表示有人加锁了
int c = getState();
// c == 0 表示没人加锁
if (c == 0) {
// hasQueuedPrecessors这里是判断AQS的等待队列是否有人在等待
// 其实公平锁和非公平锁实现的精髓就在这里,
// 公平锁如果发现AQS中等待队列有人在等待,那么直接去排队,即使资源是空的时候也不争抢
if (!hasQueuedPredecessors() &&
// 如果AQS队列没线程在排队,则CAS开始争抢锁
compareAndSetState(0, acquires)) {
// 争抢成功则设置加锁的线程时自己
setExclusiveOwnerThread(current);
return true;
}
}
// 如果上面 c > 0 说明有人加锁了
// 这里就获取当前加锁的线程是谁,如果加锁的竟然是自己,则直接重入
else if (current == getExclusiveOwnerThread()) {
// 之前加锁的是自己,现在直接重入,修改加锁的次数就好了
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
(1)调用ReentrantLock的公平锁的lock方法其实会调用到AQS的acquire方法。
acquire的方法内部其实调用子类的tryAcquire方法去实际获取锁,也就是回到了子类FairSync的tryAcquire方法,源码的流程如上图
(2)这里tryAcquire的方法其实做的事情很简单,如果判断c == 0 资源是空的,看一下等待队列有没有人,有人直接去队列后面等待,这就是公平锁啊!!!(公平锁就是判断等待队列有人就去乖乖排队了)。如果没人等待自己就去加锁。
如果资源 c > 0,说明已经有人加锁了, 则判断之前加锁的人是不是自己,如果是自己说明自己已经加过锁了,直接修改加锁的次数就好了,如果不是自己说明别人加锁了,自己自然就获取锁失败了
(3)一旦在tryAcquire获取锁失败,就会调用AQS的addWaiter方法进入等待队列排队,然后调用acquireQueued自旋的尝试获取锁或者将自己挂起,等待别人唤醒。这里的源码和核心机制,前几章我们已经非常透彻的分析过了!!。底层的进入等待队列addWaiter、以及阻塞等待的acquireQueued的这套机制还是AQS的,原来是这样啊。
公平锁FairSync释放锁逻辑
我们看到ReentrantLock释放锁的方法为unlock,底层调用的还是Sync的release方法,源码如下:
public void unlock() {
sync.release(1);
}
这里的release方法,其实就是AQS内部提供的release方法,作为释放独占锁资源的入口,我们之前讲过源码了,如下:
public final boolean release(int arg) {
// 1. 这里会调用子类的tryRelease方法,实际去释放锁
if (tryRelease(arg)) {
Node h = head;
// 2. 释放锁成功,唤醒后续AQS等待队列中等待的线程
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
上面的源码我们知道,释放锁的实际逻辑还是到子类的tryRelease方法里面,也就是FairSync的tryRelease方法里面。由于FairSync继承自Sync同步器,同时也继承了Sync定时的tryRelease方法,所以我们继续看下:
Sync的tryRelease释放锁的实际逻辑,源码如下:
protected final boolean tryRelease(int releases) {
// 直接讲释放锁的次数减少releases次
// 也就是你加锁多少次,就释放多少
// 当state == 0 的时候说明锁已经空闲了,没人持有了
int c = getState() - releases;
// 这里释放之前判断之前是不是自己加锁的
// 如果自己之前没加锁,不能胡乱释放,直接抛出异常
// 谁加的锁,谁才能释放
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 当c == 0 说明锁已经完全释放了
if (c == 0) {
free = true;
// 设置加锁的线程为null,表示没人加锁了
setExclusiveOwnerThread(null);
}
// 设置state = 0,锁空闲了,让别的线程可以加锁
setState(c);
// free = true表示锁释放了,完全空闲了
return free;
}
(1)释放锁的时候其实首先就会走入到AQS提供的release入口方法,然后release中再调用子类的tryRelease方法去释放锁
(2)tryRelease释放锁的时候会判断,是不是加锁的线程释放的,如果不是,那不行啊哥们,只能加锁的人释放锁,不然就乱了。然后释放锁的时候会扣减加锁的次数,当state 加锁的次数被扣减为零,才是真正的完全释放锁,这个时候就需要设置一下加锁的线程为null,也就是没人加锁了。
(3)当锁释放了之后,就会调用AQS给你提供的unparkSuccessor方法去唤醒等待队列中正在等待的人了,哈哈,这里的源码之前我们非常详细的讲解过了
非公平锁NonFairSync的源码
这里我们重新回顾一下上面公平锁FairSync的加锁实现,当state == 0的时候也就是没人加锁的时候,它会调用AQS的hasQueuedPredecessors()方法,判断等待队列里面是否还有人在等待。如果有人在等待那它就不去加锁了,如果没人在等它就尝试去加锁,这个点你还记得不,上面我们刚刚讲过的。
首先看一下NonFairSync非公平锁的加锁方法lock方法,作为加锁的入口方法:
final void lock() {
// 这里上来就直接尝试加锁,不管资源是不是空的,不管有没有人在等待
// 这哥们不讲武德啊,上来就抢
if (compareAndSetState(0, 1))
// 抢夺成功之后,设置是自己加锁,然后就完事了
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
看上面非公平锁的加锁lock方法:
(1)首先进入的这个方法,立马执行compareAndSetState(0,1) 尝试去加锁,这个时候不管有没有人加锁,也不管等待队列中有没有人在等,完全不讲规矩,不讲武德啊
(2)然后尝试加锁失败直接调用AQS的acquire方法,我们继续看流程:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
(3)然后acquire方法里面又会调用子类的tryAcquire方法,也就是调用NonfairSync的tryAcquire方法,实际尝试获取锁:
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
(4)NonFairSync的tryAcquire方法直接内部就是调用到Sync的nonFairTryAcquire方法,也就是说调来调去,加锁的具体逻辑最核心的源码在nonfairTryAcquire方法内部,我们下面看看:
final boolean nonfairTryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 获取资源state的值,state > 0 表示已经有人加锁了
// state = 0表示没人加锁,锁是空闲的
int c = getState();
// 如果c == 0 没人加锁,马上就去竞争锁,不管有没有人在等待
if (c == 0) {
// CAS尝试竞争锁
if (compareAndSetState(0, acquires)) {
// 加锁成功,设置加锁的线程是自己,
// 这里的setExclusiveOwnerThread就是设置时哪个线程加锁的
setExclusiveOwnerThread(current);
return true;
}
}
// 如果上面c != 0 ,说明有人加锁了;这里判断之前加锁的线程是不是自己
// 如果是自己的话,直接就重入,直接把自己加锁的次数增加就可以了
// 如果不是自己加锁,说明是别人加锁了,此时就需要进入AQS的等待队列等待
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;
}
总结
这里非公平锁NonFairSync的源码跟之前公平锁的tryAcquire方法源码几乎一致。
(1)公平锁:唯一不同的是公平锁在资源state == 0也就是没人加锁的时候,通过hasQueuedPrecessors()方法判断等待队列有没有在等待,如果有人在等待则它立马放弃去加锁。
(2)非公平锁:非公平锁在state == 0 也就是没人加锁的时候,才不管你等待队列有没有人在等待,它不在乎,比较自私一点,直接就去争抢锁,成功就返回了。
对于释放资源的实际方法tryRelease,公平锁和非公平所完全一样,都是使用Sync的tryRelease方法,这里我们上面已经讲解过了
public void unlock() {
// 调用到AQS的release方法释放
sync.release(1);
}
然后AQS中的release方法调用到子类Sync的tryRelease方法:
public final boolean release(int arg) {
// 调用到子类的tryRelease方法
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
ReentrantLock的Condition机制底层源码剖析
- ReentrantLock提供的这个Condition功能,底层还是基于AQS的Condition机制的,Condition必须要配合一个锁来使用。
- 具有的功能跟我们之前讲解过的synchronized和wait、notify/notifyAll是一样的, 实现的功能是控制多线程的行为。
这里提供的功能跟之前我们讲过的sync这个控制多线程的行为你知道啥意思不?
这个大概意思就是说:能控制每个线程在什么条件下做啥事情。比如说我想在并发安全的条件下,让线程1、线程2交叉打印数字1、数字2,实现打印效果类似:121212121212…这种。
就是控制线程的行为。下面我给你举个例子,使用Condition控制前程的行为来打印1、2。
Condition例子:两个线程交替打印1、2
public class ConditionDemo {
// 声明一个变量,初始值为1,注意:这个变量是非线程安全的
public static int value = 1;
// 声明一个reentrantLock互斥锁
public static ReentrantLock reentrantLock = new ReentrantLock();
// reentrantLock创建一个Condition
public static Condition condition = reentrantLock.newCondition();
// 线程1:始终打印1的线程
public static class PrintOneThread extends Thread {
@Override
public void run() {
// 打印10000次1
for (int i = 0; i < 10000; i++) {
try {
// 由于变量value是非线程安全的,每次操作前需加锁
reentrantLock.lock();
// 当value的值不是奇数的时候,直接沉睡等待
while (value % 2 != 1) {
// 调用condition的await方法,释放锁,同时进入沉睡
// 等待别人调用singal/singalAll唤醒自己,然后重新获取锁
condition.await();
}
// 走到这里说明value是奇数,并且自己获取了锁
System.out.print("1");
// 执行value的++操作,
value++;
// 唤醒调用condition.await而陷入等待的线程;这里就是唤醒线程2
condition.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放锁
reentrantLock.unlock();
}
}
}
}
// 线程2:始终打印2的线程
public static class PrintTwoThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
try {
// 获取独占锁
reentrantLock.lock();
// 当value的值不是偶数,就直接沉睡等待
while (value % 2 != 0) {
// 这里调用condition.await沉睡等待,同时释放独占锁
// 等待别人调用singal/singalAll唤醒自己,然后自己重新竞争锁
condition.await();
}
// 走到这里说明value是偶数,打印2
System.out.print("2");
// 执行value的++操作,让value变为奇数
value++;
// 唤醒因为调用condition.await而陷入等待的线程;这里是唤醒线程1
condition.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放独占锁
reentrantLock.unlock();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
// 创建线程1、线程2
PrintOneThread printOneThread = new PrintOneThread();
PrintTwoThread printTwoThread = new PrintTwoThread();
// 启动线程1、线程2,就会看到数字1和2交替打印的效果;实现了线程的控制
printOneThread.start();
printTwoThread.start();
// 主线程等待printOneThread、printTwoThread线程运行结束再往后走
printOneThread.join();
printTwoThread.join();
System.out.println("运行结束");
}
}
程序的运行结果,数字1、2依次交替打印,结果如下:
然后我针对上面的例子,给你画了个图,你来理解理解:
Condition.await方法源码
我们看一下Condition的await方法的源码,其实就是AQS中await方法的源码(又是基于AQS!!!),如下所示:
public final void await() throws InterruptedException {
// 如果线程被中断了,直接抛出中断异常
if (Thread.interrupted())
throw new InterruptedException();
// 当前线程封装成Node节点,加入condition队列里面
// 注意:这里是Condition队列,而不是AQS获取锁的等待队列,注意
Node node = addConditionWaiter();
// 这里是释放锁,完全释放锁资源,将state归于0
int savedState = fullyRelease(node);
int interruptMode = 0;
// 如果Node节点不在AQS获取锁的等待队列,这里一般都不会在
while (!isOnSyncQueue(node)) {
// 直接将线程挂起,让线程沉睡
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 走到这里说明有别的线程调用Condition.singal方法将你唤醒了
// 这里这里调用AQS的acquireQueue方法,这个方法的作用之前讲过了
// 就是将你放入AQS的等待队列里面,重新等待获取锁
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
// 这里就是删除一下无效的condition队列节点
unlinkCancelledWaiters();
if (interruptMode != 0)
// 由于等待时间可能太久了,被中断了
reportInterruptAfterWait(interruptMode);
}
这里我整体画一个流程图,来让你理解一下:
上面这个图形,就是Condition.await方法内部的大致流程了,由于Condition的await方法是直接调用AQS的await方法的,所以也是AQS的await方法内部的整体流程:
(1)第一步首先就是调用addConditionWaiter方法,将当前线程直接封装成一个Node加入Condition队列,注意:这里说的是Condition队列!!,不是AQS等待锁的等待队列!!,这里要注意,不要弄乱了!!!
(2)加入Condition队列之后,就是释放锁资源,这里fullyRelease就是完全释放锁,不管之前获取了锁多少次,这里都完全释放,将state 资源重新置为0。
(3)通过isOnSyncQueue方法判断是不是在AQS等待锁的等待队列,如果不是那就直接调用LockSupport.park方法将线程挂起,这里挂起之后需要别的线程调用Condition.singal或者Condition.singalAll方法才能将自己唤醒
(4)自己被唤醒之后,将自己加入AQS的等待队列,去等待队列重新等待锁!!!如果自己已经在AQS的等待队列了,跳出循环,然后在AQS的等待队列里面等待获取锁,注意:这里需要在AQS的等待队列里面等待锁,才能执行后面的业务逻辑方法。
从整体上大致知道干啥了,其实就是加入Condition队列,然后释放锁,之后沉睡。当别的线程唤醒它之后,它要重新进入AQS获取锁的等待队列里面,只有重新获取锁成功才能执行业务逻辑方法。
AQS等待队列Node类数据结构回顾
Node节点的数据结构
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;
// -2表示节点在Condition队列,等待别人唤醒
static final int CONDITION = -2;
static final int PROPAGATE = -3;
// Node节点的等待状态
volatile int waitStatus;
// AQS队列中,Node节点指向前一个节点的指针
volatile Node prev;
// AQS队列中,Node节点指向后一个节点的指针
volatile Node next;
// 线程
volatile Thread thread;
// 这里啊就是Condition队列的Node节点,指向下一个节点的指针了
Node nextWaiter;
其中prev、next指针是用在AQS等待队列的,分别指向AQS等待队列的前一个节点和后一个节点。然而nextWaiter使用在Condition队列的!!
Condition是由单向链表构成的一个队列,nextWaiter表示当前Condtion队列节点的下一个节点。
AQS内部相当于有两个队列,一个是AQS等待锁的等待队列,是一个双向链表;另一个是Condition队列,只有用到Condition机制的时候才会创建这个队列,是一个单向链表。
ConditionObject也就是Condition接口的实现类,内部也是搞了两个指针来管理着Condition队列的,给你看下代码
ConditionObject内部的数据结构
public class ConditionObject implements Condition, java.io.Serializable {
// 这里就是指向Condition队列头节点的指针
private transient Node firstWaiter;
// 这里就是指向Condition队列尾节点的指针
private transient Node lastWaiter;
}
看到了没,它也是通过两个指针来管理Condition队列的;只不过命名不一样而已。AQS等待队列使用的头节点、尾节点指针叫做head、tail,而Condition队列使用头节点、尾节点的名字叫做firstWaiter、lastWaiter而已。
再给你画个图对比一下AQS获取锁的等待队列、Condition队列:
addConditionWaiter方法源码
private Node addConditionWaiter() {
// 获取Condition队列的尾结点
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
// 如果Condition队列中已经有些节点的线程因为超时或者中断原因被取消了
// 这里的unlinkCancelledWaiters方法就是删除哪些无效的节点
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
// 创建一个Node节点
Node node = new Node(Thread.currentThread(), Node.CONDITION);
// 下面就是把node插入condition队列的尾部了
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
上面的代码很清晰吧,就是获取一下Condition队列的尾节点,然后将当前线程封装成一个Node节点,插入Condtion队列的尾部,就完事了。
当然如果发现lastWaiter节点状态不对,可能是由于超时或者中断的原因导致lastWaiter节点被取消了,此时就会调用unlinkCanceledWaiter方法删除一下无用节点。
再来看一下unlinkedCancelledWaiter方法源码:
private void unlinkCancelledWaiters() {
Node t = firstWaiter;
Node trail = null;
// 这里就是从头结点开始遍历整个链表
// 然后检查状态,状态不对的就删除
// 这里就是最基础的链表遍历和删除代码了
while (t != null) {
Node next = t.nextWaiter;
if (t.waitStatus != Node.CONDITION) {
t.nextWaiter = null;
if (trail == null)
firstWaiter = next;
else
trail.nextWaiter = next;
if (next == null)
lastWaiter = trail;
}
else
trail = t;
t = next;
}
}
这里就是一段基础的链表遍历和链表删除的操作,这里代码没啥太多的东西。
fullyRelease(释放锁)方法源码
final int fullyRelease(Node node) {
boolean failed = true;
try {
// 首先获取加锁的次数
int savedState = getState();
// 这里就是调用AQS里面的release方法去释放资源,
// 之前已经分析过了release方法的源码了
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
fullyRelease方法的源码就更简单了:
它就是直接调用release方法去全部释放资源,至于release方法的源码之前我们已经讲解过了。
我们这里再说一下,就是先调用子类的tryRelease方法去释放资源,如果释放成功了则调用AQS的unparkSuccessor方法去唤醒等待中沉睡的线程。
我们接下来再分析一下,isOnSyncQueued方法,说白了,这个方法就是遍历一下等待队列,然后依次比较,看看节点是不是在队列里面而已,就是这么简单,不信你看看下面的代码:
isOnSyncQueued方法源码
final boolean isOnSyncQueue(Node node) {
// 如果Node节点状态是CONDITION,说明肯定在CONDITION队列,不在等待队列
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
// 如果他的next指着有值,说明肯定在等待队里了
if (node.next != null)
return true;
// 这里就是遍历整个等待队列,一个个比较
return findNodeFromTail(node);
}
private boolean findNodeFromTail(Node node) {
Node t = tail;
// 从尾部往前遍历,一个个对比是否等于node节点
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
}
isOnSyncQueued方法的源码就更简单了,如果Node的next指针有值肯定在等待队列了,如果Node节点的waitStatus是CONDITION肯定就不在了,因为它会在Condition队列。然后上面的情况都不是的话就遍历整个等待队列一个个对比了。
总结
Condition.singal方法源码
public final void signal() {
// 首先判断一下自己是不是拥有独占锁
// 没有独占锁,不能调用singal方法,会抛出异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 获取Condition队列的头结点firstWaiter
Node first = firstWaiter;
if (first != null)
// 调用doSingal方法去唤醒
doSignal(first);
}
我们接着看doSingal方法的源码:
private void doSignal(Node first) {
do {
// 这里的逻辑就是从头往后遍历Condition链表
// 找到一个节点不是null的,然后调用唤醒,就那么简单
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
// 这里的实际唤醒逻辑在transferForSingal方法里面
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
继续看transferForSingal方法源码:
final boolean transferForSignal(Node node) {
// 唤醒前将节点等待状态从CONDTION改为0
// 因为后面唤醒之后还要进入等待队列去争抢锁,所以改为0也就是等待队列的初始状态
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
// 这里就是直接插入AQS等待队列了,之前讲解AQS的时候详细分析过enq源码
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
// 看,这里就是直接调用LockSupport.unpark方法将线程唤醒了
LockSupport.unpark(node.thread);
return true;
}
这里其实就是从Condition队列里面头节点开始尝试唤醒节点。唤醒之前会插入AQS的等待队列让他们再次尝试获取锁,然后就是直接调用LockSupport.unpark方法唤醒线程了,流程也不复杂。
总体来说确实不复杂,但是老规矩,为了你以后能记忆更加深刻,我还是跟你整了图:
其实还有一个Condtion.singalAll方法是唤醒Condition队列中所有等待线程的,你可以自己看一下,逻辑基本和Condition.singal一致,只是唤醒所有的而已。
Condition.await、Condition.singal的核心机制: