最近,重读源码,想着做个笔记。这次重点读的是await方法的源码。
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);// 挂起当前线程,被唤醒后,将从这里继续往下运行
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;//如果是因为中断被唤醒,则直接break
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
1.将当前线程封装成Node添加到条件队列(addConditionWaiter方法)
2.完全释放当前线程所占用的锁(由state值变为0,对于可重入锁释放之前获取的全部锁次数),保存当前的锁状态(fullyRelease方法)
3.自旋判断: 如果当前node(当前线程被封装成一个节点)不在同步队列中,则直接将当前线程挂起(isOnSyncQueue判断是否在同步队列里面,如果不在同步队列里面则会执行下面的park方法)。这里用自旋操作,如果是执行singal方法唤醒的,会将节点加入到sync队列中,到时侯线程被唤醒后,跳出自旋。然后是执行acquireQueued方法。
4.线程被唤醒后,判断是何种方式唤醒的,如果是因为中断,则跳出循环,执行的break。
这里主要分析下方法isOnSyncQueue:
final boolean isOnSyncQueue(Node node) {
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);
}
/**
* Inserts node into queue, initializing if necessary. See picture above.
* @param node the node to insert
* @return node's predecessor
*/
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;
}
}
}
}
判断在不在同步队列中,需要我们结合enq方法(将节点加入到同步队列中)来看
第一个if的判断:节点状态为 codeition,说明是在条件队列中,没有前节点说明不在同步队列中(sync队列是双向的)
第二个if条件:如果有后续节点(node的next属性有值那么它一定在sync队列中),说明当前节点一定在同步队列中。
为啥还会用到findNodeFromTail方法判断是否在同步队列中,就需要看enq方法了。
enq将一个新节点的入队分了三步:
node.prev = t
tail
属性,使它指向当前节点所以prev不为空时,我们无法判断节点到底在不在sync中。因为cas操作可能失败,那么这个结点还没有在sync中,如果cas成功,那么它在sync中。但是我们这个方法判断节点是否在sync中,为啥是采用从尾节点开始逆向遍历链表。是因为从前往后遍历是不准的,prev有值的节点不一定在sync中,还有就是节点的next属性无值时,实际上新节点已经是tail了(对应的就是第2步成功,第三步还没有执行)。如果我们逆向遍历,只要tail(volatile修饰的)节点的prev指向这个节点(第2步发生了,则第1步一定发生),那么这个节点一定在sync中。
最后,findNodeFromTail方法 从尾节点开始遍历,判断是否在sync队列中。(对应节点已经加入进去同步队列了,但是最后设置next属性还没执行的情况)
private boolean findNodeFromTail(Node node) {
Node t = tail;
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
}
线程被挂起后,如果要被唤醒,则有俩种方式:被中断,signal方法唤醒当前线程。checkInterruptWhileWaiting方法进行判断中断的方式。0代表未被中断(即通过signal唤醒的),THROW_IE代表在signal之前被中断了,REINTERRUPT代表是中断发生在signal之后。
/**
* Checks for interrupt, returning THROW_IE if interrupted
* before signalled, REINTERRUPT if after signalled, or
* 0 if not interrupted.
*/
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
一共三种被唤醒的情况,现在分析最简单的情况,同时也是大多数情况正常的signal:
1)唤醒后,回到 while (!isOnSyncQueue(node))这一步,这时候,节点在同步队列里面(被signal唤醒则绝对在sync队列中<查看signal的源码得出的结论>,由于已经发生过signal了,则此时node必然已经在sync queue
中),所以isOnSyncQueue
将返回true,我们将退出while循环。接下来执行的就是
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
acquireQueued方法是唤醒后,去抢锁的逻辑,这个过程如果发生了中断则返回true,则修改interruptMode为REINTERRUPT。否则中断状态为0,之后不会抛出中断异常。
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
将已经取消的节点,取消掉。
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
如果发生了中断,则抛出一个中断的异常。
2)中断发生时,线程还没有被signal过 ,对应状态:THROW_IE
/**
* Checks for interrupt, returning THROW_IE if interrupted
* before signalled, REINTERRUPT if after signalled, or
* 0 if not interrupted.
*/
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
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用于判断是何种中断导致唤醒的,现在我们分析返回true的情况,也就是THROW_IE的中断状态。待会我们分析另一种中断情况。
判断一个node是否被signal过,一个简单有效的方法就是判断它是否离开了condition queue
, 进入到sync queue
中。
换句话说,只要一个节点的waitStatus还是Node.CONDITION(await方法第一步加入条件队列,这时候状态就是Node.CONDITION),那就说明它还没有被signal过。
/**
* Adds a new waiter to wait queue.
* @return its new wait node
*/
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;
}
这时候,执行CAS操作(compareAndSetWaitStatus(node, Node.CONDITION, 0)),状态改为0。
接下来执行的就是,将这个node加入到同步队列中(enq(node)),然后返回true(说明是THROW_IE的中断状态),这时候,while循环,执行break。接下来,第一个if条件((acquireQueued(node, savedState)争夺锁的方法)会直接跳过,第二个if条件(if (node.nextWaiter != null))判断是否还有后节点(条件队列),有则断开后节点的连接。接下来,抛出中断异常(查看reportInterruptAfterWait方法代码)。
3)中断发生时,已经发生过了signal的情况。对应中断状态:REINTERRUPT
分了俩种情况:
3.1)被唤醒时,已经发生了中断,但此时线程已经被signal过了。对应状态:REINTERRUPT
回到之前第二种情况的判断逻辑处:transferAfterCancelledWait方法,while循环判断当前节点是否在同步队列中,如果在,直接跳出循环,然后返回fasle,对应REINTERRUPT状态。如果,还不在同步队列中(处于从条件队列移除后在去往同步队列的路上)。这时候会先挂起,直到下次被唤醒,然后继续执行while处逻辑,判断是否在同步队列中。然后执行break,剩下的分析和前面一样。最后,执行reportInterruptAfterWait方法,这里并没有抛出中断异常,而只是将当前线程再中断一次。
3.2)被唤醒时,并没有发生中断,但是在抢锁的过程中发生了中断。对应状态:REINTERRUPT
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)这个条件返回是0,但是在接下来的acquireQueued(node, savedState)的抢夺锁时,发生了中断,最后执行reportInterruptAfterWait方法,这里并没有抛出中断异常,而只是将当前线程再中断一次。