1)条件队列概念:
条件队列是当某个条件不满足状态时,挂起自己并释放锁,一旦等待条件为真,则立即醒来。这也是条件队列提供的主要功能。
Object的wait/notify/notifyAll等方法构成了内部条件队列的API,在lock中又是怎么实现的呢?
就是这里要讨论的条件队列 condition。
具体使用示例就不展示了,和 wait/notify/notifyAll 使用场景类似,接下来直接看源码。
2)源码分析:
condition一般会结合lock使用,获取方式如下:
ReentrantLock reentrantLock = new ReentrantLock();
Condition condition = reentrantLock.newCondition();
通过内部类 sync 获取condition,因为多个线程共享一个lock,所以这里的conditionObject也是多个线程共享。
final ConditionObject newCondition() {
return new ConditionObject();
}
先看一张流程图:
接下来具体分析上面的几个核心方法:
public final void await() throws InterruptedException {
//响应中断锁
if (Thread.interrupted())
throw new InterruptedException();
//在此Condition维护的等待队列中增加节点
Node node = addConditionWaiter();
//挂起线程之前,必须释放当前锁,这里调用 reentrantLock的释放逻辑实现。
int savedState = fullyRelease(node);
int interruptMode = 0;
//判断当前的线程是否在同步队列中,如果不在当前队列中,直接挂起当前线程,等待被 notify 唤醒。
while (!isOnSyncQueue(node)) {
//如果当前的node的WaitStatus是Condition,那么park此线程
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//当该线程被 notify唤醒之后,调用独占锁的逻辑去抢锁
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
接下来一步步看:
首先是增加线程节点逻辑,每次增加线程节点的时候都会剔除状态非condition的节点。
private Node addConditionWaiter() {
Node t = lastWaiter;
//t为最后一个等待节点,如果不为空,则表示有线程在等待队列排队
if (t != null && t.waitStatus != Node.CONDITION) {
//如果当前节点的状态不是 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;
}
下面是判断当前线程是否在同步队列中:
final boolean isOnSyncQueue(Node node) {
//如果当前线程为condition状态,或者prev为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);
}
如果这里返回了true,那么表示已经在队列中,那么继续争抢锁。
这种主要场景是已经释放锁,如果返回true,证明当前线程已经被唤醒(已经执行过signal方法)。如果返回false,证明没有singal,所以要挂起当前的线程。
这样await的逻辑就清晰了,接下来是一个signal的过程,调用signal的时候:
public final void signal() {
//检查当前线程是不是,Lock占用的线程
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//取出Condition队列中第一个Node
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
在唤醒方法 doSignal 中会执行以下逻辑:
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
Node p = enq(node);
int ws = p.waitStatus;
//这里设置节点的状态为 Node.SIGNAL,这里如果设置不成功,也会挂起线程
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
上面设置节点的状态为 Node.SIGNAL,这里如果设置不成功,也会挂起线程,等待持有锁的线程释放锁后,唤起当前节点。
友链:探果网