AQS 共享式同步状态获取和释放
上一篇文章中主要分析了 AQS 的独占模式对同步状态的获取和释放过程,本文主要分析下共享模式下的同步状态的获取和释放是如何实现的
共享锁获取
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
// 获取锁失败
doAcquireShared(arg);
}
private void doAcquireShared(int arg) {
// 不同于独占模式下,创建的节点模式不同
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
// 不同独占模式
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
从 doAcquireShared 实现可以看出,共享模式下同步队列的节点在自旋的过程与独占模式基本类似,不同在于自旋过程中成功获取同步状态时的处理
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node);
// 若 progagate > 0 说明可继续向下传播唤醒节点
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
从 setHeadAndPropagate 的实现我们可以看出在移动 head 节点之后,若满足继续往下传播唤醒的条件时将会调用 doReleaseShared 方法。
共享锁释放
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
// 释放同步状态
doReleaseShared();
return true;
}
return false;
}
``
```java
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
// 将 head 节点状态重置为 0
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
// CAS 成功则唤醒下个节点
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
// 头节点指向未发生变化的时候 退出循环
if (h == head) // loop if head changed
break;
}
}
因共享模式下,会存在多个线程同时释放同步状态的场景, doReleaseShared 通过不断的轮询和 CAS 操作保证节点的唤醒。
我们还是以图的形式模拟下多线程释放的场景: