尝试阅读ReentrantLock、AbstractQueuedSynchronizer源码 (一)
这篇文章希望通过对 AbstractQueuedSynchronizer
内部类 ConditionObject
的探索,加深对阻塞唤醒机制的理解!
一、await()
方法流程
ReentrantLock lock = new ReentrantLock(true);
Condition con = lock.newCondition();
con.await();
这一段代码很常见,当调用await方法,阻塞当前线程。下面我们来看看具体实现:
AbstractQueuedSynchronizer.ConditionObject.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;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
加入condition队列
下面详细看下addConditionWaiter
AbstractQueuedSynchronizer.ConditionObject.addConditionWaiter()
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;
}
通过这段代码,其实不难发现:
1、每个condition
有一个关联的队列,并且这个队列是个单向链表;
2、每个condition
队列头节点为firstWaiter
、尾节点为lastWaiter
。使用nextWaiter
进行彼此之间的关联
到现在为止,我们知道了两个队列,同步队列(阻塞队列)和条件队列(
condition
队列),那么这两个队列之间有什么关联呢? 值得我们关注
unlinkCancelledWaiters
方法用于清除队列状态不为-2的节点。
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;
}
}
释放线程持有的锁
线程被阻塞时,同时需要放弃自己所持有的锁,并唤醒后继节点。最后将节点状态改为-2。在释放锁时,如果当前线程不是锁持有者,则抛出IllegalMonitorStateException
异常。
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
//将当前节点状态修改为-2;当添加下一个节点时,该节点将会被清除
node.waitStatus = Node.CANCELLED;
}
}
等待进入同步队列
//判断当前线程是否在同步队列中
final boolean isOnSyncQueue(Node node) {
//如果节点状态为-2,或者无前驱节点,返回false
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
//如果next属性不为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.
*/
// 从队尾遍历
return findNodeFromTail(node);
}
线程不断自旋来确认是否还在同步队列中,如果不在同步队列中了,则阻塞当前线程;原来await
方法使用的就是park
。
二、signal()
方法流程
使用该用法用于唤醒一个阻塞的线程,该方法只有锁持有者可以调用,否则抛出异常。
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
private void doSignal(Node first) {
do {
//将firstWaiter指向第二个等待的节点
//如果第二个等待的节点为null,让尾节点修改为null
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
//去掉与第二个等待节点的管理
first.nextWaiter = null;
}//如果转移失败,则循环转移下一个节点,直到成功
while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
//将节点node转移到同步队列
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
* 如果无法修改node节点是waitStatus,说明node节点的状态为1。
* 进行条件队列下一个节点的转移
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
//进入阻塞队列尾部,p表示前驱节点
Node p = enq(node);
int ws = p.waitStatus;
//如果前驱节点状态为1(已经取消),或者改变前驱节点的状态为-1失败,则直接唤醒node节点
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}