doReleaseShared ,一个比较特殊的方法,由于共享的特性,在获取锁和释放锁的过程都需要唤醒后继节点,因为可以有多个线程同时进入临界区。这个方法的主要作用是:
1.在执行 acquireShared 申请资源时,执行 tryAcquireShared 失败则执行 doAcquireShared 方法将线程加入同步队列,然后判断同步队列中是否只有当前这一个等待线程,是则自旋获取锁,如果获取到了锁,就把当前节点设为头结点。然后 setHeadAndPropagate 继续唤醒后继节点,因为上次释放的资源可能特别多,现在的资源支持较多的线程同时执行,所以要唤醒后继节点。唤醒后继节点的条件是 资源数 > 0 或者 当前节点的后继节点的 waitStatus < 0 在这里只能是 PROPAGATE 或者 SIGNAL。这里也就解释了为什么在 doReleaseShared 中需要设置头为 PROPAGATE 状态,就是为了后续的唤醒。
2.而在执行 releaseShared 释放资源时,首先执行 tryReleaseShared ,如果成功则执行 doReleaseShared 释放后继节点,因为释放了资源让更多的线程来运行。如果说现在队列中没有任何节点在等待就把头结点设置为 PROPAGATE ,说明有过剩的资源可用。如果此时刚好遇到上面执行 acquireShared 需要唤醒后继节点的时候先要判断头结点的 waitStatus 的值,如果是 PROPAGATE 肯定可以唤醒后面的。如果说等待队列中有等待线程那么就唤醒他们就行。
3.好了这个 doReleaseShared 方法需要在以上两种情况下分析,否则始终不清楚为什么要设置 PROPAGATE 。
// 这个方法中与 release 中的唤醒不同点在于他保证了释放动作的传递
// 如果后继节点需要唤醒,则执行唤醒操作,如果没有后继节点则把头设置为 PROPAGATE
// 这里的死循环和其他的操作中的死循环一样,为了检测新的节点进入队列
// 其实这个方法比较特殊,在 acquireShared 和 releaseShared 中都被执行了,主要就是共享模式允许多个线程进入临界区
private void doReleaseShared() {
for (;;) {
Node h = head;
// 等待队列中有正在等待的线程
if (h != null && h != tail) {
// 获取头节点对应的线程的状态
int ws = h.waitStatus;
// 如果头节点对应的线程是SIGNAL状态,则意味着头结点正在运行,后继结点所对应的线程需要被唤醒。
if (ws == Node.SIGNAL) {
// 修改头结点,现在后继节点要成为头结点了,状态设置初始值
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
// 唤醒头结点h的后继结点所对应的线程
unparkSuccessor(h);
}
// 队列中没有等待线程,只有一个正在运行的线程。
// 将头结点设置为 PROPAGATE 标志进行传递唤醒
else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
// 如果头结点发生变化有一种可能就是在 acquireShared 的时候会调用 setHeadAndPropagate 导致头结点变化,则继续循环。
// 从新的头结点开始唤醒后继节点。
if (h == head) // loop if head changed
break;
}
}
// 被 acquireShard 调用
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
// 当前线程设为头结点
setHead(node);
// propagate 代表的是当前剩余的资源数,如果还有资源就唤醒后面的共享线程,允许多个线程获取锁,
// 或者还有一个比较有意思的条件就是 h.waitStatus < 0 他其实是说 h.waitStatus 要么是 signal 要么是 propagate
// 从而唤醒后继节点
if (propagate > 0 || h == null || h.waitStatus < 0 || (h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}