可以发现ConditionObject类是Condition的子类,那么接着看看Condition:
发现就是个接口,里面提供了一些方法,根据方法名可以猜测,大致是阻塞等待和唤醒的方法,暂不深入,留个印象。
返回ConditionObject类看看它有什么切入口可以让我们去理解它的作用,看看它拥有的方法:
发现还是看不出什么来,那么换一个切入点,找一个AQS子类相关的同步器来作为突破口,这里使用ReentrantLock,那么就先找找ReentrantLock哪里用到了ConditionObject类:
从上述截图可以发现,ReentrantLock类的newCondition()方法会返回一个ConditionObject对象。
那么ConditionObject对象的创建来源就找到了,那么找找ReentrantLock是怎么使用它的?回想之前截图中Condition接口提供的方法,ConditionObject对象的使用肯定是围绕着这些方法做展开和扩展的,先看看Condition接口的await()方法在子类的具体实现:
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//这里会新建出一个node对象
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()这个方法:
//该方法的主要作用就是找出有效的尾节点,如果有效尾节点为null,则初始化队列,队列头尾节点都是要新加入的线程相关节点,否则新节点设置为尾节点
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
Condition对象必须通过Lock对象的newCondition()方法才能获取,通过上述的addConditionWaiter()方法,只要获取到同步状态的首节点调用了await()方法,该节点相关的线程就会被重新封装成一个新的节点,最后加入到ConditionObject相关的队列中,再看看AQS同步器的队列,也有维护头节点和尾节点对象,如下:
他们也是由一个个Node对象连接起来的链表,只不过AQS本身的队列是双向链表,而ConditionObject相关的队列是单向的。
现在可以知道AQS中的首节点获取到同步状态后,调用await(),就会从AQS的队列进入到ConditionObject的队列,那么ConditionObject的队列中的节点又是怎么进入到AQS的队列的?
接下来看看signal()方法:
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//取得ConditionObject队列中的首节点
Node first = firstWaiter;
//首节点不为null,说明完成了初始化,同时队列中有节点存在
if (first != null)
doSignal(first);
}
//该方法主要作用是找到没有被取下的节点,如果唤醒失败,说明节点被取消了,那么继续往下找
private void doSignal(Node first) {
do {
//这里先取得首节点的后继节点赋值给firstWaiter,firstWaiter为null,则说明队列中只有一个节点了
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
//把首节点的后继节点设为null,因为首节点要出队了
first.nextWaiter = null;
//循环做唤醒操作,唤醒失败,则说明节点被取消了,那么就把firstWaiter赋值成首节点,继续循环
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
重点在transferForSignal()方法:
final boolean transferForSignal(Node node) {
//如果这步设置不成功,说明节点不处于Node.CONDITION状态,那就是被取消了,返回fasle,取消的节点无需唤醒
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
//如果设置成功,那么就调用AQS的enq()方法,这里简述下enq()方法作用,就是把节点设置为尾节点加入到AQS的队列中,这里很重要的一点需要提醒,返回的p对象是原来队列的尾节点,也就是现在新加入节点的前驱节点
Node p = enq(node);
//得到新加入节点的前驱节点的状态
int ws = p.waitStatus;
//如果该新节点的前驱节点已经被取消,或者设置等待状态失败,那么就唤醒新节点,重新去做同步,保证能拼接到有效的尾节点
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
//唤醒新节点
LockSupport.unpark(node.thread);
return true;
}
所以当前线程调用了signal()方法,会取出ConditionObject队列中的有效首节点进入AQS队列中,成为AQS队列新的尾节点。
从以上方法可以得知ConditionObject的队列是如何和AQS的队列做交互的。
由于ConditionObject的队列存放的是等待的节点,所以可以把它的队列称之为等待队列,相反的AQS队列中的节点在自旋阻塞尝试获取同步状态,所以把AQS的队列称之为同步队列。
总结
ConditionObject的队列称为等待队列,AQS的队列称为同步队列,同步队列的首节点调用await()方法,该节点相关的线程会被封装成新节点进入等待队列,当前线程调用signal()方法,会取出等待队列中的首节点插入到同步队列的队尾,同步队列和等待队列就是如此做交互的。