上一次我们对Reentrant Lock的加解锁的源码进行了解析,对背后的实现原理有了相应的了解,但并不仅限于加解锁功能,Reentrant Lock还支持等待唤醒功能,让我们来看看怎么实现的吧
1.await
1.1 入口
public final void await() throws InterruptedException {
//如果当前线程存在中断标记,抛出中断异常
if (Thread.interrupted())
throw new InterruptedException();
//将当前线程封装成node节点添加至等待队列
Node node = addConditionWaiter();
//这里是个完全释放锁的过程
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
//封装的node节点没有在同步队列中时,会被阻塞
LockSupport.park(this);
//TODO 后面的代码都是唤醒后的逻辑,我们先看线程是如何被唤醒的
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);
}
1.2 addConditionWaiter方法
等待队列队尾插入一个waitStatus=Condition的节点,在这个过程中会根据队尾节点的状态来判断是否需要执行清除无效节点的逻辑
private Node addConditionWaiter() {
Node t = lastWaiter;
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;
}
//等待队列的重构
private void unlinkCancelledWaiters() {
Node t = firstWaiter;
Node trail = null;
// AQS的等待队列是一个单向队列,所以只能从头向尾进行便利
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;
}
}
1.3fullyRelease
因为ReentrantLock是可重入锁,进入等待需要完全释放锁,
如果完全释放锁失败,入队的等待节点会被取消等待状态,在下一个等待节点入队的时候,会被清除掉
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)
node.waitStatus = Node.CANCELLED;
}
}
//释放锁并返回释放的结果
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
//同步队列的队头如果有效,进行唤醒操作
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
//进入等待需要取得锁
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
//完全释放锁,相应的独占线程会被清空
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
//唤醒同步队列队头节点的下一个节点
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
在释放锁的过程,如果同步队列中存在同步节点,会对改节点进行唤醒(unparkSuccessor)操作,在这里我们看到unparkSuccessor的入参是队头节点,而我们在上一篇加解锁原理解析中得知,队头节点是已经竞争到锁的节点,那这岂不是有问题吗?
我们进入unparkSuccessor方法,会看到通过对队头节点的下一节点的有效状态(节点不为空且未进入等待队列)判断可以看到,如果节点无效,会从队尾依次往前轮询拿到一个在离队头节点最近的有效的同步节点,所以最终唤醒的是队头节点的下一个有效节点.
针对释放锁的过程,我有一个疑问,在unparkSuccessor方法中是不是还缺少对队头节点的next变量的置空操作??
释放锁之后线程会被挂起,后一个有效节点会被唤醒执行业务逻辑,为了更好的理解等待唤醒,我们先看唤醒逻辑
2.signal
signal这里的逻辑很简单,主要从等待队列取出队头节点,将其放置入同步队列,这个放置过程中,如果上一个同步节点无效,则直接唤醒这个等待节点。
public final void signal() {
//如果未获得锁,会抛出异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//等待队列的队头节点出队
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
//等待队列存在等待节点,执行唤醒
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) {
//如果取消等待,立即返回,不进行唤醒node节点
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
//将等待节点更新从等待状态更新至初始状态,并添加至同步节点并返回队尾,注意,这个返回的是最终队尾节点的上一个节点
//如果上一个同步节点已取消等待,则将等待节点唤醒
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
到这里唤醒逻辑已经结束了,我们回过头来再看唤醒之后的逻辑
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);
}
//线程被唤醒后的校验,未被中断返回0
//如果当前状态为condition,且成功初始化等待状态,返回THROW_IE,否则返回REINTERRUPT
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;
}
3.总结
await方法主要做了如下几项事情:
3.1.1 将当前线程封装成node节点(节点的waitStatus=condition),添加至等待队列,在入队的过程中,如果上一个队尾节点取消了等待,会对等待队列中的无效节点清除操作。
3.1.2 完全释放锁,
如果未完全释放,该等待节点会被取消等待,
释放成功,清空锁的独占标识,并唤醒同步队列的队头节点的下一个有效节点,然后再挂起当前线程
3.1.3 被唤醒后,首先先校验同步节点的中断标识位,如果中断,且依然waitStatus=condition,抛出异常,如果中断,且正常入队(同步队列),会在竞争锁(重入锁次数=等待过程中完全释放锁的次数)成功之后安全中断
signal方法主要做了如下几项事:
3.2.1 等待队列的队头出队,如果队头节点无效(已取消),执行一次清除等待队列无效的等待节点操作
3.2.2 将出队的等待节点进行入队(同步队列入队,入队之后的waitStatus=0)操作,如果上一个队尾节点已取消同步,会立即唤醒这个入队的等待节点,
4.扩展
4.1不可中断等待 - awaitUninterruptibly
不可中断等待和等待的区别就是等待过程中不会抛出异常
4.2超时自动取消等待-awaitUntil(Date deadline)和awaitNanos(long nanosTimeout)
如代码所示,超时取消等待的逻辑就是在等待的基础上增加了时间的限制,该时间一到,线程会被自动唤醒,在transferAfterCancelledWait方法会将对应的等待节点添加至同步队列
public final boolean awaitUntil(Date deadline)
throws InterruptedException {
long abstime = deadline.getTime();
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
boolean timedout = false;
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
if (System.currentTimeMillis() > abstime) {
timedout = transferAfterCancelledWait(node);
break;
}
LockSupport.parkUntil(this, abstime);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
return !timedout;
}
4.3 全部唤醒-signalAll
与signal不同的是,signalAll会唤醒全部的等待节点