一、简介
ReentrantLock 中ConditionObject内部类,实现了Condition接口,内部维护了一个单向链表(firstWaiter、lastWaiter),而Node中的nextWaiter正是用于该单链表
await:释放当前锁持有的锁,生成线程等待node,存储到condition中的单链表中,等被唤醒的时候,在加入到锁的等待队列
signal:唤醒condition等待队列里的一个线程(firstWaiter)
signalAll: 循环唤醒condition等待队列里的所有线程

二、方法详解
1、await方法详解
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//在condition队尾增加一个等待节点
Node node = addConditionWaiter();
//释放全部锁,并且唤醒同步队列中下一个等待线程
int savedState = fullyRelease(node);
int interruptMode = 0;
//节点是否添加到了同步队列
while (!isOnSyncQueue(node)) {
//不在同步队列,挂起当前线程
LockSupport.park(this);
//interruptMode
//线程未中断,返回0
//线程中断了,设置该节点状态-2 —> 0 成功,返回-1 THROW_IE
//线程中断了,设置该节点状态-2 —> 0 失败,返回1 REINTERRUPT
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//acquireQueued做的事,申请锁(公平锁看队列有没有正在排队的,非公锁则直接抢锁),若没申请下,则park线程,等待unpark唤醒;若申请下,则返回boolean,true代表线程中断,false代表线程未中断
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
//清理node.nextWaiter引用
unlinkCancelledWaiters();
if (interruptMode != 0)
//interruptMode == REINTERRUPT,则设置线程中断标识
//interruptMode == THROW_IE 抛出线程中断异常
reportInterruptAfterWait(interruptMode);
}
await流程图

再看方法checkInterruptWhileWaiting中的transferAfterCancelledWait
final boolean transferAfterCancelledWait(Node node) {
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
enq(node);
return true;
}
/*
* If we lost out to a signal(), then we can't proceed
* until it finishes its enq(). Cancelling during an
* incomplete transfer is both rare and transient, so just
* spin.
*/
while (!isOnSyncQueue(node))
Thread.yield();
return false;
}
其中 transferAfterCancelledWait方法中,将node的状态从CONDITION(-2)修改为0的这步,修改成功的话,则返回 true,即中断模式 THROW_IE(意味这接下来就要抛出 InterruptedException),这是因为,signal会将node的状态修改为0,如果这里修改成功,说明signal还没有修改状态,线程唤醒发生在signal之前,这种情况一般出现在await一定时间上,等待超时了。如果transferAfterCancelledWait方法修改node状态失败,说明signal已经修改了node的状态,从CONDITION(-2)修改为0,然后判断signal是否将节点添加到了同步队列里,没有则自旋等待,让出CUP时间。
2、signal&signalAll方法详解
public final void signal() {
//当前持有锁的线程不是自己,则抛出异常(说明signal执行的前提是获取到锁)
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
public final void signalAll() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignalAll(first);
}
private void doSignal(Node first) {
do {
//将firstWaiter后移,指向下个节点,这里是将node从condition的等待队列移除
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
//transferForSignal修改node状态,并将node添加到同步队列,成功返回true,失败false,成功就终止循环(只唤醒一个),失败则处理下一个node
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
private void doSignalAll(Node first) {
//清空condition队列(first备份了)
lastWaiter = firstWaiter = null;
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
//transferForSignal修改node状态,并将node添加到同步队列,成功返回true,失败false,因为是全部唤醒,所以不关心返回结果
transferForSignal(first);
first = next;
//循环处理等待队列的每个节点,直到处理完
} while (first != null);
}
signal流程(此处省略signalAll,signalAll就是内部循环transferForSignal)

再看方法doSignal中的transferForSignal
final boolean transferForSignal(Node node) {
//这里修改节点状态,与await中修改节点状态竞争,
//如果这里修改失败了,说明线程已经唤醒,且在await中修改了节点状态
//如果修改成功了,则await中的线程可能唤醒了可能没唤醒,唤醒的话就等待该方法将节点添加到同步队列,没有唤醒的话,就等待唤醒
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
//添加节点到同步队列
Node p = enq(node);
int ws = p.waitStatus;
//前置节点状态大于0,则说明前置节点已经撤销,唤醒当前节点线程
//修改前置节点状态为Node.SIGNAL,修改失败的话唤醒当前节点线程(其实此处不知道为啥会失败,官方注释说会出现状态异常,没什么影响)
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
三、await、signal、signalAll中同步队列、等待队列节点变化
假设前提:thread1占有锁,thread2在同步队列等待,thread0在condition队列等待
1、同步器状态变化
当thread1执行await,其中会调用releaseAll,将同步器状态修改为0,将exclusiveOwnerThread修改为null,然后调用unparkSuccessor,唤醒thread2,thread2申请下锁,将同步器状态修改为1,设置exclusiveOwnerThread为thread2

2、同步队列、等待队列变化



