AbstractQueuedSynchronizer 队列同步器源码分析-共享锁

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 操作保证节点的唤醒。
我们还是以图的形式模拟下多线程释放的场景:

image
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容