1. Condition简介
之前在介绍AQS源码的时候,讲述了同步队列的独占模式和共享模式,排队都是在同步队列中。
但是实际上,AQS中还有条件队列。我们举个例子来解释条件队列的作用,例如我们排队去上厕所,通过排队最终获得了锁进入了厕所,但是不巧的是发现忘记带纸,遇到这种事情很无奈,但是也得接受这个事实,这时只能乖乖的出去准备好手纸(也就是进入了条件队列中等待),当然再出去之前还要把锁释放掉,好让后面排队的人进来,在准备好了手纸(条件满足)条件满足之后进入同步队列中去重新排队;
可能在平时我们比较少使用Condition队列,但是在一些并发容器的源码中经常会看到Condition的身影,例如:LinkedBlockingQueue, ArrayBlockingQueue等等。
Condition包含的两个方法是:await()和signal()。和Object.wait()、Object.notify()方法是相对应的。此外必须注意的是:执行await/signal方法,必须先获取锁,否则会抛出异常。这就和必须先执行synchronizer才能执行wait/notify方法一样。
2. 条件队列结构
以ReentrantLock为例,如果我们需要使用条件队列,就需要通过newCondition()方法创建Condition对象:
public Condition newCondition() {
return sync.newCondition();
}
接着又会继续进入Sync类中的newCondition()方法:
final ConditionObject newCondition() {
return new ConditionObject();
}
这个ConditionObject对象实际上是AQS的内部类,实现了Condition接口:
public class ConditionObject implements Condition, java.io.Serializable {
我们再进入Condition接口,看看具体包含哪些方法:
public interface Condition {
// 响应中断的条件等待
void await() throws InterruptedException;
// 忽略中断的条件等待
void awaitUninterruptibly();
// 设置有限等待时间的条件等待(不支持自定义时间单位)
long awaitNanos(long nanosTimeout) throws InterruptedException;
// 设置有限等待时间的条件等待(支持自定义时间单位)
boolean await(long time, TimeUnit unit) throws InterruptedException;
// 设置绝对时间的有限等待
boolean awaitUntil(Date deadline) throws InterruptedException;
// 唤醒条件队列中的头节点
void signal();
// 唤醒条件队列中的所有节点
void signalAll();
}
可以看到,方法其实都是await和signal方法,分别就是将当前线程进入条件队列和唤醒条件队列中的线程;
接下来回到ConditionObject类,其中有两个重要的成员变量:
/** First node of condition queue. */
private transient Node firstWaiter;
/** Last node of condition queue. */
private transient Node lastWaiter;
firstWaiter和lastWaiter分别指向等待队列中的第一个节点和最后一个节点。
此外,还有点需要注意的是,在这个Condition条件队列中,它是一个单向队列。和我们之前讲的AQS中的双向同步队列是不同的。我们通过一段代码来验证这个点:
public static void main(String[] args) {
final ReentrantLock lock = new ReentrantLock();
final Condition condition = lock.newCondition();
for(int i=0;i<10;i++){
Thread thread = new Thread(){
@Override
public void run() {
lock.lock();
try {
condition.await();
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
};
thread.start(); // 断点
}
}
在代码中,我们按照顺序创建了十个线程,并且都执行了condition.await()方法,然后debug停留在第十个线程的断点处:
从上图可以看到,虽然条件队列和同步队列中的节点都是AQS中的Node内部类,但是在条件队列中并没有使用prev和next指针,只使用了nextWaiter指向下一个节点,所以Condition条件队列是单向的。如下图:
此外,还有点要注意的是。一个Lock能够持有多个条件队列,也就是能够多次调用lock.newCondition()方法。如下图:
相比较,AQS中的同步队列就只能有一个。
3. await实现原理
首先我们进入conditionObject.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;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE) // 出了条件队列后,进入同步队列中
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters(); // 去除取消状态的节点
if (interruptMode != 0) // 是否中断
reportInterruptAfterWait(interruptMode);
}
从上面的代码中,我们可以看到首先是会将节点加入到条件队列中,进入到addConditionWaiter()方法中:
private Node addConditionWaiter() {
Node t = lastWaiter; // 队尾节点
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) { // 队尾节点不是Condition
unlinkCancelledWaiters(); // 消除队列中所有不是condition状态的节点
t = lastWaiter; // 获得新的队尾
}
Node node = new Node(Thread.currentThread(), Node.CONDITION); // 创建新节点
if (t == null) // 如果是空队列
firstWaiter = node; // 头节点指针指向node
else
t.nextWaiter = node; // 否则队尾元素的next指向node
lastWaiter = node; // node成为新的尾节点
return node;
}
在addConditionWaiter()方法中,我们可以很清晰看到就是进行了一个简单的入队操作。其中会调用unlinkCancelledWaiters()消除队列中的非condition状态的节点,于是我们进入unlinkCancelledWaiters()方法:
private void unlinkCancelledWaiters() {
Node t = firstWaiter; // 获得头指针指向的节点
Node trail = null; // 指代上一次遍历的最后一个节点
while (t != null) { // 遍历
Node next = t.nextWaiter; // 获得t的后继节点
if (t.waitStatus != Node.CONDITION) { // 如果节点t不是condition状态
t.nextWaiter = null; // t节点出队
if (trail == null)
firstWaiter = next; // 更新头指针指向的节点
else
trail.nextWaiter = next;
if (next == null) // 如果是最后一个节点了
lastWaiter = trail; // 将trail节点成为新的尾指针指向的节点
}
else // 如果t是condition状态
trail = t;
t = next; // 更新t节点
}
}
在unlinkCancelledWaiters()函数中就是从头节点遍历到尾节点,抛弃掉不是condition状态的节点,进行出队。
至此,addConditionWaiter()和unlinkCancelledWaiters()函数都介绍完了。回到await(),接下来会进入fullyRelease(node):
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState(); // 获取state同步状态
if (release(savedState)) { // 释放锁
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException(); // 如果当前锁不是当前线程持有,就报错
}
} finally {
if (failed) // 如果失败了,就cancelled
node.waitStatus = Node.CANCELLED;
}
}
从fullyRelease函数中可以看到,这个函数是用来充分释放锁的,当然前提是你得先持有当前锁,否则就会报错。
再回到await()函数中,接下来就会判断while (!isOnSyncQueue(node)),第一次判断的时候肯定是为false的,会进入到while循环中。我们下面看看isOnSyncQueue(node)的代码:
final boolean isOnSyncQueue(Node node) {
// 判断当前状态是否为condition或者前驱节点为null
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
// 判断是否有后继节点
if (node.next != null) // If has successor, it must be on queue
return true;
/*
* node.prev can be non-null, but not yet on queue because
* the CAS to place it on queue can fail. So we have to
* traverse from tail to make sure it actually made it. It
* will always be near the tail in calls to this method, and
* unless the CAS failed (which is unlikely), it will be
* there, so we hardly ever traverse much.
*/
return findNodeFromTail(node); // 进行遍历查找
}
在这个方法中,如果不符合前两个条件,就会进入findNodeFromTail(node)函数从后往前遍历,查看在同步队列中是否有这个节点:
private boolean findNodeFromTail(Node node) {
Node t = tail; // 同步队列尾指针指向的位置
for (;;) { // 遍历
if (t == node)
return true;
if (t == null)
return false;
t = t.prev; // 不断寻找前驱
}
}
至此,经过上面的方法就能判断出当前节点是否在同步队列上了。
接着,我们回到await方法中,进入while(isOnSyncQueue(node))循环后,就会通过LockSupport阻塞。跳出循环就只有两种方式:解除阻塞进入到同步队列或遇到中断。
跳出循环后,这是当前节点已经在同步队列上面了,就会进入acquireQueued方法(AQS独占锁中已经介绍过)去排队尝试获取锁,获取锁成功后会接着调用unlinkCancelledWaiters()方法删除掉取消状态的节点。
至此,await()函数就结束了。方法的示意图如下:
4. signal实现原理
首先我们进入到conditionObject的signal()方法中:
public final void signal() {
if (!isHeldExclusively()) // 检测当前线程是否获得lock
throw new IllegalMonitorStateException();
Node first = firstWaiter; // 获取condition队列的头节点
if (first != null)
doSignal(first); // signal
}
我们可以看到,首先是检查当前线程是否持有lock。然后接着会调用doSignal方法:
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null; // 将头节点从等待队列中移除
} while (!transferForSignal(first) && // 对头节点进行唤醒
(first = firstWaiter) != null);
}
可以看到,首先是将头节点移出condition队列,然后是调用transferForSignal(first)方法对头节点唤醒:
final boolean transferForSignal(Node node) {
// CAS将condition状态切换成默认状态,如果失败就表明头节点是cacel状态
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
Node p = enq(node); // 对头节点进行同步队列入队操作
int ws = p.waitStatus;
// 调用unpark方法进行唤醒
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
在transferForSignal(Node node)方法中,我们看到他是先将头节点加入到同步队列的末尾去排队,然后调用unpark方法的。这样就能够让await()函数里面调用park方法的线程解除阻塞,跳出循环。
至此signal()方法就介绍玩了,整体的示意图如下:
5. signalAll实现原理
signal和signalAll方法的区别在于,前者只会唤醒头节点的线程,而signalAll方法会唤醒condition条件队列里的所有线程。我们进入到signalAll()方法:
public final void signalAll() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignalAll(first); // 区别:调用doSignalAll方法
}
这个方法的区别就是会调用doSignalAll()方法:
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
} while (first != null); // 和doSignal方法的区别
}
这个方法唯一的区别就是在while循环的条件部分,使得会循环不断唤醒条件队列中的线程。
6. 总结
我们总结下整个流程,图中的等待队列就是条件队列:
- 线程A处于同步队列中,竞争中获得锁
- 线程A在执行过程中调用Condition.await()方法,线程A释放锁,进入到condition条件队列中
- 当线程A成为条件队列头部时,并且其他线程调用signal方法时,就会进入到同步队列的尾部进行排队
- 线程A重新尝试获得锁
7. 例子
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionTest {
private static ReentrantLock reentrantLock = new ReentrantLock();
private static Condition condition = reentrantLock.newCondition();
private static boolean flag = false;
public static void main(String[] args) {
Thread awaiter = new Thread(new Awaiter());
Thread signaler = new Thread(new Signaler());
awaiter.start();
signaler.start();
}
static class Awaiter implements Runnable {
@Override
public void run() {
reentrantLock.lock();
try {
while (!flag) {
System.out.println("Awaiter在等待资源");
condition.await();
}
System.out.println("Awaiter在获取资源成功");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
}
}
static class Signaler implements Runnable {
@Override
public void run() {
reentrantLock.lock();
try {
flag = true;
System.out.println("Signaler配置资源");
condition.signal();
System.out.println("Signaler唤醒队列");
} finally {
reentrantLock.unlock();
}
}
}
}
输出结果:
代码中创建了两个线程,Awaiter和Signaler,Awaiter需要等待flag=true资源,执行了Await方法。Signaler则会将flag设置成true,并执行signal方法唤醒Awaiter。