-
fullyRelease 方法 (AQS)
final int fullyRelease(Node node) {
boolean failed = true;
try {
// 获取当前节点的 state
int savedState = getState();
// 释放锁
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
fullyRelease 方法是由 AQS 提供的,首先获取当前的 state,然后调用 release 方法进行释放锁。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
release 方法在 AQS 中做了详细的介绍。它的主要作用就是释放锁,并且需要注意的是:
-
fullyRelease 会一次性释放所有的锁,所以说不管重入多少次,在这里都会全部释放的。
-
这里会抛出异常,主要是在释放锁失败时,这时就会在 finally 里面将节点状态置为 Node.CANCELLED。
-
isOnSyncQueue(node)
通过上面的流程,节点已经放到了条件队列并且释放了持有的锁,而后就会挂起阻塞,直到 signal 唤醒。但是在挂起时要保证节点已经不在同步队列(SyncQueue)中了才可以挂起。
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;
return findNodeFromTail(node);
}
// 从尾部开始遍历
private boolean findNodeFromTail(Node node) {
Node t = tail;
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
}
如果一个节点(总是一个最初放置在条件队列中的节点)现在正等待在同步队列上重新获取,则返回 true。
这段代码的主要作用判断节点是不是在同步队列中,如果不在同步队列中,后面才会调用 park 进行阻塞当前线程。这里就会有一个疑问:AQS 的同步队列和 Condition 的条件队列应该是无关的,这里为什么会要保证节点不在同步队列之后才可以进行阻塞?因为 signal 或者 signalAll 唤醒节点之后,节点就会被放到同步队列中。
线程到这里已经被阻塞了,当有其他线程调用 signal 或者 signalAll 时,会唤醒当前线程。
而后会验证是否因中断唤醒当前线程,这里假设没有发生中断。那 while 循环的 isOnSyncQueue(Node node) 必然会返回 true ,表示当前节点已经在同步队列中了。
后续会调用 acquireQueued(node, savedState) 进行获取锁。
final boolean acquireQueued(final Node node, int arg) {
// 是否拿到资源
boolean failed = true;
try {
// 中断状态
boolean interrupted = false;
// 无限循环
for (;;) {
// 当前节点之前的节点
final Node p = node.predecessor();
// 前一个节点是头节点, 说明当前节点是 头节点的 next 即真实的第一个数据节点 (因为 head 是虚拟节点)
// 然后再尝试获取资源
if (p == head && tryAcquire(arg)) {
// 获取成功之后 将头指针指向当前节点
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// p 不是头节点, 或者 头节点未能获取到资源 (非公平情况下被别的节点抢占)
// 判断 node 是否要被阻塞,获取不到锁就会一直阻塞
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
这里就是 AQS 的逻辑了,同样可以阅读 AQS 的相关介绍。
>1. 不断获取本节点的上一个节点是否为 head,因为 head 是虚拟节点,如果当前节点的上一个节点是 head 节点,则当前节点为 第一个数据节点>
;
>2. 第一个数据节点不断的去获取资源,获取成功,则将 head 指向当前节点;
>3. 当前节点不是头节点,或者 tryAcquire(arg)
失败(失败可能是非公平锁)。这时候需要判断前一个节点状态决定当前节点是否要被阻塞
(前一个节点状态是否为 SIGNAL)。
值得注意的是,当节点放到 AQS 的同步队列时,也是进行争抢资源,同时设置 savedState
的值,这个值则是代表当初释放锁的时候释放了多少重入次数。
总体流程画图如下:
![图片[1]-Java ConditionObject 介绍(二)-Java专区论坛-技术-SpringForAll社区](https://static001.geekbang.org/infoq/96/961ed7a26871516f46e0a312eb88db8f.png)
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 头节点指向条件队列头的下一个节点
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
// 将原来的头节点和同步队列断开
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
// 判断节点是否已经在之前被取消了
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
// 调用 enq 添加到 同步队列的尾部
Node p = enq(node);
int ws = p.waitStatus;
// node 的上一个节点 修改为 SIGNAL 这样后续就可以唤醒自己了
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
enq 同样可以阅读 AQS 的代码
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 方法将节点放到 AQS 的同步队列之后,要将 node 的前一个节点的 waitStatus 设置为 Node.SIGNAL。signalAll 的代码也是类似。
总结
Q&A
Q: Condition 和 AQS 有什么关系?
A: Condition 是基于 AQS 实现的,Condition 的实现类 ConditionObject 是 AQS 的一个内部类,在里面共用了一部分 AQS 的逻辑。
Q: Condition 的实现原理是什么?
A: Condition 内部维护一个条件队列,在获取锁的情况下,线程调用 await,线程会被放置在条件队列中并被阻塞。直到调用 signal、signalAll 唤醒线程,此后线程唤醒,会放入到 AQS 的同步队列,参与争抢锁资源。
Q: Condition 的等待队列和 AQS 的同步队列有什么区别和联系?
A: Condition 的等待队列是单向链表,AQS 的是双向链表。二者之间并没有什么明确的联系。仅仅在节点从阻塞状态被唤醒后,会从等待队列挪到同步队列中。
没有回复内容