在看本文前,如果对ReentrantLock不了解的,请先看 ReentrantLock源码分析,以免有些地方无法理解。
代码的执行说明,都已注释的方式写在了代码中,如果想看某个方法的分析,直接搜索方法名即可。当然,本文中对于在ReentrantLock中详细说明过的方法以及相关分析(如将节点加入到同步队列的方法 enq,将节点挂起的方法acquireQueued,waitstatus状态说明等),在这里没有详细说明,请直接看ReentrantLock源码分析
Condition.await
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//将当前线程封装为Node节点加入到wait队列中
Node node = addConditionWaiter();
//在wait将项城挂起前释放锁
//在释放锁的同时,会那单线程的重入次数State。后面再讲线程重新挂起到lock上的时候,会还原原有的state
int savedState = fullyRelease(node);
int interruptMode = 0;
//判断当前节点是否是已经在同步队列上了,如果已经在同步队列上了,就不需要挂起。
//当然,这里将线程挂起后,当线程被single唤醒,将通过这个判断跳出循环,因为在single将线程唤醒后会同时将线程Node加入到同步队列中。
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
//如果Node节点是中断唤醒的,则通过这里跳出循环
//这里需要注意的一个点是给interruptMode赋值了,interruptMode 记录了不同的中断方式(抛异常还是interupt)
//通过后面的分析可以看到,如果Node是因为中断导致将Node加入到Lock,则将中断方式设置为抛异常。
//也就是线程还在wait队列中时候被中断了,则对外的终端是抛异常告知。如果是在lock中被中断,则告知的方式是通过interupt方式。
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//执行到这里,说明被wait的线程已经唤醒了,并且已经将线程加入到了同步队列中。
//这里首先通过acquireQueued将加入到同步队列中的Node节点挂起
//一定要注意:一旦线程在这里通过acquireQueued挂起,线程将不会再继续往下执行,只有当线获取锁之后,才会从这里继续往下执行。
//并且这里挂起的时候,会还原线程原本的重入次数savedState 。
//如果一旦获取了锁,会再次判断是否是抛异常,如果不是,设置中断方式就是interupt的方式
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
//这里判断Node节点的nextWaiter 是否等于空。
//通过这个判断当前节点是否还继续链接在wait队列上,如果还继续链接在wait队列上,就将其从wait队列上删除。
//通过后面对addConditionWaiter的分析,你会看到,wait是单向队列,节点之间是通过addConditionWaiter连接的。
//通过后面分析,你会发现,如果是中断将节点加入到了同步队列,并不会将wait队列上的节点删掉。
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
//这里根据不同中断方式,对外进行不同的中断相应
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
//这个方法的作用,就是将当前线程封装成为Node节点并加入到wait队列。
//wait是一个单向队列,firstWaiter 指向头结点,lastWaiter指向尾巴节点,节点之间通过nextWaiter链接,并且处于wait队列上的正常的节点的waitStatus 状态都为CONDITION(如果被唤醒并加入到同步队列,就会将其改为0)
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
//这里判断尾节点的waitStatus是否是CONDITION,如果不是,则通过unlinkCancelledWaiters删除那些不符合条件的节点。
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
//从wait队列上删除不符合条件的节点后,就将当前线程节点链接到wait队列上。
//这里不用考虑安全问题,因为既然要调用wait必须要先获得锁。
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
//将队列上,waitStatus 不等于CONDITION的节点去掉。
//这个逻辑很简单,就是简单的链表操作,就不逐句分析了.
//遍历链表,维护了三个连续指针,中间指针一旦waitStatus 不等于CONDITION,就将其从节点上去掉。
private void unlinkCancelledWaiters() {
Node t = firstWaiter;
Node trail = null;
while (t != null) {
Node next = t.nextWaiter;
if (t.waitStatus != Node.CONDITION) {
t.nextWaiter = null;
if (trail == null)
firstWaiter = next;
else
trail.nextWaiter = next;
if (next == null)
lastWaiter = trail;
}
else
trail = t;
t = next;
}
}
//释放锁,这里之所以使用fullyRelease,意思就是如果一个锁多次重入,那么就需要一次性将多次重入一同释放。
//这对于ReentrantLock来说当前是很简单的,只需要将state设置成0即可。
final int fullyRelease(Node node) {
boolean failed = true;
try {
//获取当前线程持有的state。state也代表了锁的重入次数
int savedState = getState();
//将原state作为参数,一次性全部释放掉。释放成功了,返回wait前持有的锁的个数
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
//判断当前节点是否已经存在于同步队列上了
final boolean isOnSyncQueue(Node node) {
//如果已经放入了同步队列。waitStatus一定不为CONDITION,并且他的prev一定不为NULL。
//所以满足下面的任何一个条件,说明都没有放到同步队列上。所以返回false。
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
//如果不为null,说明一定在同步队列上了,返回true。
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.
*/
//前面的两个if如果满足条件就立即返回了,如果都不满足,就只有遍历同步队列来逐个检查了,当然比较浪费时间。
return findNodeFromTail(node);
}
//遍历lock逐个比较
private boolean findNodeFromTail(Node node) {
Node t = tail;
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
}
//判断是否中断,如果线程没有中断,则返回0。如果中断了,则通过transferAfterCancelledWait判断是否是放入lock前发生的中断。
//如果是,则通过抛异常响应中断,如果不是,则通过interupt的方式响应中断
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
//判断是否是放入lock前发生的中断
final boolean transferAfterCancelledWait(Node node) {
//如果当前节点的waitstatus为Node.CONDITION,则当前节点就一定还在wait队列上。因为已经发生了中断,所以就需要将其放入到同步队列。
//放入之前,先通过CAS将waitstatus替换为0。如果替换失败了,说明其他线程正在将当前节点放入到lock。
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
//如果替换成功了,则通过enq将节点放入到同步队列
enq(node);
//说明是在wait中发生的中断,则返回true
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.
*/
//上一步说,如果CAS替换失败,说明有其他线程正在将Node放入到同步队列中,但是不已经已经放入成功了,可能只是修改了waitstatus,还没有最终放入,所以这里如果判断还没有完全放入成功的时候,先通过死循环加yield自旋等待一会。
while (!isOnSyncQueue(node))
Thread.yield();
//知道到这,说明是其他线程已经将节点放入了同步队列,说明不是在wait队列中发生的中断。
return false;
}
//通过不同的中断方式,对外进行中断的响应
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
selfInterrupt();
Condition.signal
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
//如果wait队列有被wait的节点,则进行唤醒第一个节点
if (first != null)
doSignal(first);
}
//唤醒wait队列上的第一个节点
private void doSignal(Node first) {
do {
//判断是否队列上只有一个节点,如果是,就将lastWaiter 设置为null
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
//既然要唤醒第一个节点,唤醒后,直接将第一个节点从队列上移除。
first.nextWaiter = null;
//通过transferForSignal将节点加入到同步队列中。如果加入失败了,则重新取出wait队列的第一个节点并唤醒。
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
//将wait节点放入到同步队列中
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
//这里如果CAS失败,说明当前节点的状态已经不是CONDITION,说明已经被其他线程唤醒了。
//返回false之后,外层是一个循环,会继续取出wait队列的第一个节点,继续执行唤醒操作
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).
*/
//将当前节点加入到同步队列,注意,这里enq返回值返回的是假如队列节点的前一个节点,也就是原队尾节点。
Node p = enq(node);
int ws = p.waitStatus;
//这里的判断非常巧妙,我们知道,我们把Node节点从wait移动到同步队列,如果我们把节点线程唤醒,是没有问题的。
//因为唤醒后执行到await中的acquireQueued的时候,会被重新挂起,但是这样比较耗费性能,是没有必要的。
//所以这里进行了判断,如果移入到同步队列后,发现原尾节点的状态大于0,或者将尾节点的状态改为SIGNAL的时候失败了,才会唤醒。并在acquireQueued中重新整理同步队列并重新挂起。
//这里不挂起是没有问题的,因为在acquireQueued挂起前判断,如果当前节点是第一个节点,会直接获取锁。如果中断唤醒了,或继续从await挂起的地方继续执行,会继续在acquireQueued的地方重新挂起。
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
Condition.signalAll
public final void signalAll() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//拿到wait队列,执行doSignalAll
Node first = firstWaiter;
if (first != null)
doSignalAll(first);
//理解了signal,理解这里的doSignalAll就很简单了。
//就是遍历wait队列上的节点逐个顺序取出放入到同步队列中。
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
} while (first != null);
}