全网最详细的AbstractQueuedSynchronizer(AQS)源码剖析(三)条件变量
前两期我们已经掌握了AQS的基本结构、以及AQS是如何释放和获取资源的。其实到这里,我们已经掌握了AQS作为同步器的全部功能
不过,有些情况使用同步功能不够灵活,所以AQS又引入了操作系统中的另一个高度相关的概念——条件变量。由于条件变量的使用紧密依赖于AQS提供的释放、获取资源功能和同步队列,因此都放在了AQS源码中
能坚持看到这里的同学已经很不容易了,再接再厉,一起冲掉最后一座堡垒吧🦾🦾🦾
简介
条件变量是什么
条件对象这一概念源自于操作系统,设计它是为了解决等待同步需求,实现线程间协作通信的一种机制。Java其实也已经内置了条件变量,它和监视器锁是绑定在一起的,即Object
的wait
和notify
方法,使用这两个方法就可以实现线程之间的协作
Java中的条件变量直到Java 5才出现,用它来代替传统的Object
的wait
和notify
方法。相比wait
和notify
,Condition
的await
和signal
方法更加安全和高效,因为Condition
是基于AQS实现的,加锁、释放锁的效率更高
条件变量顾名思义就是表示某种条件的变量。不过需要说明的是,条件变量中的条件并没有实际含义,仅仅只是一个标记,条件的含义需要代码来赋予
Condition接口
Condition
是一个接口,条件变量都实现了Condition
接口。该接口的基本方法是await
、signal
、signalAll
这些
不过Condition
依赖于Lock
接口,需要借助Lock.newCondition
方法来创建条件变量。因此Condition
必然是和某个Lock
绑定在一起的,这就和wait
和notify
和Object
的监视器锁绑定在一起一样。因此,Java中的条件变量必须配合锁使用
Condition
和Java内置的条件变量方法之间的对应关系如下:
Condition.await
等价于Object.wait
Condition.signal
等价于Object.notify
不多BB了,直接看源码吧~
AQS之条件对象
AQS为条件变量的实现提供了95%以上的功能,Lock
接口实现类一般只需要实现一下newCondition
方法,以及AQS的isHeldExclusively
方法,就可以直接使用条件变量了,你说方不方便!
AQS实现的方式就是直接提供了Condition
的一个实现类——ConditionObject
,我接下来就将其称为条件对象(可能不太严谨)。ConditionObject
是AQS的内部类,可见性为public
条件对象的结构
每个ConditionObject
都维护了一个条件队列,首尾节点分别由firstWaiter
、lastWaiter
两个域来管理:
/** First node of condition queue. */
private transient Node firstWaiter;
/** Last node of condition queue. */
private transient Node lastWaiter;
每个在条件队列中等待的线程都由Node
类来维护,它们之间通过Node
类的nextWaiter
相连,形成一个单向链表。每个Node
的waitStatus
都是Node.CONDITION
,表明该线程正在某个条件变量的条件队列中等待ing~
条件对象的创建
十分的朴实无华且简单通透,要是所有代码都这么简单,那该有多好啊~
public ConditionObject() { }
正如前面所说,如果基于AQS实现的Lock
接口实现类想要使用AQS提供的条件变量,只需要实现newCondition
方法、isHeldExclusively
方法即可。而实现newCondition
方法一般只需要直接调用ConditionObject
的构造方法即可,多么简单!
isHeldExclusively
方法会在条件唤醒(signal
、signalAll
)中被调用,因为只有在持有互斥资源的情况下,才允许使用条件变量,在持有共享资源的情况下是不允许使用的!
接下来主要剖析一下ConditionObject
是如何实现Condition
接口的两大重要功能——条件等待、条件唤醒
版权:本文版权归作者和博客园共有
转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任
条件等待
await
await
方法就是最基本的条件等待,它是可以被中断的。源码如下:
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 能够离开while循环,说明被signal或被中断了,而且在同步队列中,所以可以调用acquireQueued
// 而且之前释放了多少资源,现在就要还原回来,因此获取的参数是之前保存的state
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
addConditionWaiter
方法会添加一个新节点到等待队列的队尾,该节点的waitStatus
是CONDITION
。源码如下:
private Node addConditionWaiter() {
Node t = lastWaiter;
// 如果lastWaiter被取消(CANCELLED),就移除它以及前面所有被取消的节点
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION); // 为当前线程创建一个Node,waitStatus为CONDITION
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
unlinkCancelledWaiters
会将所有被取消的Node
从条件队列中移除,该方法只会在持有锁的情况下才会被调用,源码如下:
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 == nul)
lastWaiter = trail;
}
else
trail = t;
t = next;
}
}
unlinkCancelledWaiters
相当于是进行了一次正向遍历,将那些waitStatus
不为CONDITION
(即为CANCELLED
)的Node
给移除,挺简单的~
回到await
方法,接下来调用fullyRelease
方法保存当前的state
,并调用一次release
,最后返回保存的state
。源码如下:
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED; // 如果失败就会将节点取消
}
}
调用release
就是为了完全释放当前线程所持有的资源,防止死锁。如果释放失败,说明当前线程压根没有获取到资源就调用了await
方法,这是不允许的,会抛出IllegalMonitorStateException
异常。这也解决了我之前的疑惑——如何保证调用unlinkCancelledWaiters
之前必须持有锁呢? 这里就给出了答案!只有在当前线程持有资源的前提下,代码才能正常执行下去
回到await
方法,接下来调用isOnSyncQueue
方法来检查当前线程对应的Node
在同步队列上还是条件队列上,源码如下:
final boolean isOnSyncQueue(Node node) {
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
if (node.next != null) // If has successor, it must be on queue
return true;
return findNodeFromTail(node);
}
该方法会先看目标node
的waitStatus
是否为CONDITION
,或者它的prev
域是否为空,如果是,那么它一定在条件队列上,返回false;如果目标node
的next
域不为空,那么它一定在同步队列上,返回true。如果以上两种快捷判断方法无法判定,那么需要调用findNodeFromTail
进行暴力判定。总之,如果目标node在条件队列上等待,则返回true,否则说明在同步队列上,返回false
await
中的while(!isOnSyncQueue)
语义:如果当前节点所等待的条件没有满足,那么说明它还在条件队列上等待,就会一直被困在while循环中不停地被阻塞,不能离开while循环去竞争获取资源
findNodeFromTail
方法会从tail
开始向前遍历,以确定目标node
是否位于同步队列上,很简单哦~其源码如下:
// 此方法只会被isOnSyncQueue方法调用
private boolean findNodeFromTail(Node node) {
Node t = tail;
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
}
回到await
方法,只有当node
在条件队列上等待,才会执行while
循环。在循环中,将当前线程(即node
对应的线程)阻塞。如果被唤醒,就调用checkInterruptWhileWaiting
方法检查是否在阻塞过程中被中断,如果发生了中断,该方法会返回非0值。while
循环中如果发现被中断,则退出循环
checkInterruptWhileWaiting
方法即其调用到的transferAfterCancelledWait
方法,它们的源码如下:
/*
* 不同返回值的含义:
* 0:没有发生中断
* THROW_IE(-1):中断发生在被signal之前
* REINTERRUPT(1):中断发生在被signal之后
*/
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
/*
* transferAfterCancelledWait有两个作用:
* 1、将目标node放到同步队列上
* 2、检测node是否已经被signal,true表示还未被signal
final boolean transferAfterCancelledWait(Node node) {
// 如果CAS成功,说明该node还未接收到signal信号
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
enq(node);
return true;
}
// 如果上面if中的CAS失败,说明node已经被signal
// 那么就等待signal中的逻辑将node移动到同步队列上去,这里什么也不做,自旋等待即可
while (!isOnSyncQueue(node))
Thread.yield();
return false;
}
总之,如果线程在阻塞过程中没有发生中断,则checkInterruptWhileWaiting
返回0,否则返回非0值
具体来说,如果发生中断,就会调用transferAfterCancelledWait
方法将node
转移到同步队列,并检测中断是发生在被signal
之前还是之后。如果发生在被signal
之前则返回THROW_IE(-1),如果发生在被signal
之后则返回REINTERRUPT(1)
回到await
方法的while
循环中,如果没有被signal
或被中断就会一直阻塞。如果被signal
就不满足while
条件,会退出循环;如果被中断而不是被signal
,会直接break
离开while
循环
退出while
循环说明此时已经位于同步队列(要么因为被signal
而transfer到同步队列,要么因为被中断而调用transferAfterCancelledWait
方法移动到同步队列),可以直接调用acquireQueued
方法(调用该方法的前提:位于同步队列中)排队获取锁。获取虽然可能导致再次被阻塞,但这里是在同步队列上的阻塞,而不是在条件队列上的阻塞
退出处理是通过reportInterruptAfterWait
方法,具体如何处理,取决于interruptMode
的值。源码如下:
private void reportInterruptAfterWait(int interruptMode) throws InterruptedException {
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
selfInterrupt();
}
如果interruptMode
是THROW_IE,那么说明该线程并未真正收到signal
信号,只是因为被中断而被唤醒,所等待的条件并不一定被满足,于是抛出中断异常;如果是REINTERRUPT
,调用selfInterrupt
方法设置线程的中断状态
总的来说,await
执行分为6步:
1、如果当前线程中断,那么抛出中断异常
2、为当前线程创建Node
,并加入该条件变量的条件队列队尾
3、获取并保存下当前state
,并使用保存的state
来调用release
方法,如果失败则抛出IllegalMonitorStateException
异常
4、阻塞直到被signal
或被中断
5、利用保存的state
,调用acquireQueued
方法重新获取状态
6、如果第四步期间被中断,则抛出中断异常
超时await
await
方法有一个重载版本,可以设置超时参数,如果超时也会导致等待停止。其源码如下:
public final boolean await(long time, TimeUnit unit)
throws InterruptedException {
long nanosTimeout = unit.toNanos(time);
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
final long deadline = System.nanoTime() + nanosTimeout;
boolean timedout = false;
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
if (nanosTimeout <= 0L) {
timedout = transferAfterCancelledWait(node);
break;
}
if (nanosTimeout >= spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
nanosTimeout = deadline - System.nanoTime();
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
return !timedout;
}
带**超时参数**的`await`和无参数`await`基本差不多,执行一共分6步,不过最后一步会**返回是否超时**: 1、如果当前线程中断,那么抛出中断异常 2、为当前线程创建`Node`,并加入该条件变量的条件队列队尾 3、获取并保存下当前`state`,并使用保存的`state`来调用`release`方法,如果失败则抛出`IllegalMonitorStateException`异常 4、阻塞直到被`signal`或被中断,或**超时** 5、利用保存的`state`,调用`acquireQueued`方法重新获取状态 6、如果第四步期间被中断,则抛出中断异常;如果第四步是**因为超时而停止阻塞**,则返回false,否则返回true
awaitUninterruptibly
调用awaitUninterruptibly
方法在等待过程中,不会因为中断而停止等待。源码如下:
public final void awaitUninterruptibly() {
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
boolean interrupted = false;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if (Thread.interrupted())
interrupted = true;
}
if (acquireQueued(node, savedState) || interrupted)
selfInterrupt();
}
`awaitUninterruptibly`执行分4步: 1、为当前线程创建`Node`,并加入该条件变量的条件队列队尾 2、获取并保存下当前`state`,并使用保存的`state`来调用`release`方法,如果失败则抛出`IllegalMonitorStateException`异常 3、阻塞直到被`signal` 4、利用保存的`state`,调用`acquireQueued`方法重新获取状态
awaitNanos
awaitNanos
最多等待一定时间,可以被中断。其源码如下:
public final long awaitNanos(long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
final long deadline = System.nanoTime() + nanosTimeout;
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
// 如果超时,则将node从条件队列转移到同步队列上去,并退出while循环
if (nanosTimeout <= 0L) {
transferAfterCancelledWait(node);
break;
}
if (nanosTimeout >= spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
nanosTimeout = deadline - System.nanoTime();
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
return deadline - System.nanoTime();
}
`awaitNanos`执行分6步: 1、如果当前线程中断,那么抛出中断异常 2、为当前线程创建`Node`,并加入该条件变量的条件队列队尾 3、获取并保存下当前`state`,并使用保存的`state`来调用`release`方法,如果失败则抛出`IllegalMonitorStateException`异常 4、阻塞直到被`signal`或被中断或**超时** 5、利用保存的`state`,调用`acquireQueued`方法重新获取状态 6、如果第四步期间被中断,则抛出中断异常;最后**返回距离超时还剩多少纳秒**
awaitUntil
类似于awaitNanos
,会返回是否是因为超时而终止等待。源码如下:
public final boolean awaitUntil(Date deadline)
throws InterruptedException {
long abstime = deadline.getTime();
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
boolean timedout = false;
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
if (System.currentTimeMillis() > abstime) {
timedout = transferAfterCancelledWait(node);
break;
}
LockSupport.parkUntil(this, abstime);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
return !timedout;
}
`awaitUntil`和`awaitNanos`基本差不多,执行一共分6步,不过最后一步会返回是否超时: 1、如果当前线程中断,那么抛出中断异常 2、为当前线程创建`Node`,并加入该条件变量的条件队列队尾 3、获取并保存下当前`state`,并使用保存的`state`来调用`release`方法,如果失败则抛出`IllegalMonitorStateException`异常 4、阻塞直到被`signal`或被中断或超时 5、利用保存的`state`,调用`acquireQueued`方法重新获取状态 6、如果第四步期间被中断,则抛出中断异常;如果第四步是因为超时而停止阻塞,则返回false,否则返回true
版权:本文版权归作者和博客园共有
转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任
条件唤醒
signal
signal
用于唤醒某个等待在条件队列的线程。源码如下:
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
首先调用isHeldExclusively
方法来判断当前线程是否持有互斥资源(isHeldExclusively
方法只会在signal
或signalAll
中才会被调用),如果没有则抛出IllegalMonitorStateException
异常。接下来调用doSignal
方法将等待最久(队首)的线程从条件队列转移到同步队列
这里说明一下,只有当线程持有互斥资源时,才支持使用条件变量。关于这点,可以参考全网最详细的ReentrantReadWriteLock源码剖析(万字长文)
其中,写锁支持条件变量,而读锁不支持。因为写锁是互斥资源,而读锁是共享资源。原因也可以参见这篇博客里的解析,这里不再赘述
doSignal
方法会从目标node
(一般都是队首)开始,将遇到的第一个未被取消的线程从条件队列移除,并调用transferForSignal
方法将其放到同步队列队尾去等待获取资源。源码如下:
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) && (first = firstWaiter) != null);
}
transferForSignal方法不仅会判断目标node是否被取消,也会将目标node从条件队列移动到同步队列。源码如下:
final boolean transferForSignal(Node node) {
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) // 如果CAS失败,说明node已经被取消了,直接返回false
return false;
Node p = enq(node); // 入队,返回node的前驱节点p
int ws = p.waitStatus;
// 入队之后希望将前驱节点置为SIGNAL,表明node的线程正在苦等获取资源
// 如果前驱节点已被取消,或CAS修改前驱的waitStatus失败,就干脆直接将node的线程唤醒,不多bb
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
总之,`signal`方法会将最早的(非取消)线程移动到同步队列去排队获取资源
signalAll
signalAll
方法会将条件队列中所有的线程都移动到同步队列,让它们去获取资源。其源码如下:
public final void signalAll() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignalAll(first);
}
该方法会调用doSignalAll
方法将条件队列中所有的线程都移除,并放入同步队列。源码如下:
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null; // 将条件队列清空
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
} while (first != null);
}
总之,`signalAll`方法会将条件队列中所有的线程都移动到同步队列去排队获取资源
版权:本文版权归作者和博客园共有
转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任
条件变量的应用
实现生产者-消费者模型
使用上面的非可重入锁NonReentrantLock
的条件变量来实现简单的生产者-消费者模型(单生产者、单消费者),实现代码如下:
点击查看代码
public class ProducerConsumerModel {
private static final NonReentrantLock lock = new NonReentrantLock();
private static final Condition notFull = lock.newCondition();
private static final Condition notEmpty = lock.newCondition();
private static final Queue<String> queue = new LinkedList<>();
private static final int queueLength = 10; // 队列长度
public static void main(String[] args) {
Thread producer = new Thread(() -> {
int i = 0;
while (true) {
lock.lock();
try {
while (queue.size() == queueLength) {
System.out.println("Queue is full!");
notFull.await();
}
Thread.sleep(2000);
System.out.println("Produce product: " + "product" + i);
queue.add("product" + i);
++i;
notEmpty.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
});
Thread consumer = new Thread(() -> {
while (true) {
lock.lock();
try {
while (queue.isEmpty()) {
System.out.println("Queue is empty!");
notEmpty.await();
}
Thread.sleep(2000);
String product = queue.poll();
System.out.println("Consume product: " + product);
notFull.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
});
producer.start();
consumer.start();
}
}
好了,AQS系列共三期,到此为止,Bye~
全网最详细的AbstractQueuedSynchronizer(AQS)源码剖析(一)AQS基础
全网最详细的AbstractQueuedSynchronizer(AQS)源码剖析(二)资源的获取和释放
全网最详细的AbstractQueuedSynchronizer(AQS)源码剖析(三)条件变量
出处:https://www.cnblogs.com/frankiedyz/p/15676704.html
版权:本文版权归作者和博客园共有
转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任