在上篇中,已经分析简单分析了lock和unlock的原理,本篇中,主要分析Condition原理。
- await方法
public final void await() throws InterruptedException {
//线程被中断,直接抛出中断异常
if (Thread.interrupted())
throw new InterruptedException();
//向条件队列中添加一个
Node node = addConditionWaiter();
//记录await前重入了几次
int savedState = fullyRelease(node);
//**********这一步骤后,其他线程就可以进程lock了
int interruptMode = 0;
//节点不在同步队列中,挂起当前线程
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//在同步队列中,就需要重新抢锁
//*******此处可能会有疑惑,我都在condition队列中了,怎么还会在同步队列中????
//这里需要结合signal代码来看
//抢锁的时候,还是原来的重入次数
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
复制代码
单独看上上面的代码确实是一脸疑惑,怎么还会在同步队列中,下面先看下signal做了啥
public final void signal() {
if (!isHeldExclusively())
//非持锁线程,抛出异常
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
复制代码
doSignal会将条件队列的Node转移到同步队列,
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
复制代码
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
//只有节点处于cancelled状态才会返回false,在非中断的各种模式中,不会出现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;
//因为这两步不是原子操作,所以下面cas失败是有可能的,失败的情况下,
//此线程一定被设定为SIGNAL状态,可以看lock源码分析
//执行LockSuupport.park,那么unpark给个令牌,无论park是先执行还是后执行都不会堵塞
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
复制代码
那下面接着await,判定节点是否在同步队列中,
final boolean isOnSyncQueue(Node node) {
//当Node状态由CONDITION变为0,且还未执行enq的时候,node.dev就是null,说明还没到同步队列
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
//有不为0状态的后驱,一定是在同步队列中
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.
*/
//下面描述的enq方法的cas可能会失败
//非原子的操作,很多种肯能,此时另一个线程正在lock,将其节点添加到tail后,
//那么当前从条件队列到同步队列的Node就是node.prev!=null node.next=null
//下面的方法从头到尾遍历
return findNodeFromTail(node);
}
复制代码
enq方法
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
复制代码
最简单流程分析:
- T1,T2,T3三个线程抢锁,同步队列如下
2. T1线程进行condition.await
此时T1会释放锁,由于没有新来的线程竞争,那么下一个获取锁就是T2
- T2拿到锁后,执行condition.signal()
T1从条件队列到同步队列,等待唤醒(T1已经park挂起了,必须T2执行完unlock后,后续激活才会执行T1的unpark)