AbstractQueuedSynchronizer共享锁源码分析

205 阅读2分钟

介绍

与独占锁相对应的就是共享锁,顾名思义共享锁是指多个线程可以同时持有锁进行自身业务的处理,比较常见的使用就是ReentrantReadWriteLock的读锁,我们下一章节对ReentrantReadWriteLock进行分析,在这篇文章里我们先来了解下AQS的同步队列如何对共享锁进行处理的

共享锁的获取

还是直接从代码源头开始看起吧

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0) // 返回值大于0获取共享锁成功,反之失败,进入队列中进行尝试获取
        doAcquireShared(arg);
}

tryAcquireShared方法由子实现类进行实现,这里我们可以先不用过多关注,只需要了解当该方法返回值大于0时代表获取共享锁成功,反之就是失败

private void doAcquireShared(int arg) {
    final Node node = addWaiter(Node.SHARED); // 创建nextWaiter为SHARED的Node节点
    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);
    }
}
private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head;
    setHead(node); // 将当前节点设置成头结点
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;
        if (s == null || s.isShared()) // 如果后置节点不为null,且nextWaiter为SHARED模式,对后置节点进行唤醒
            doReleaseShared();
    }
}

通过阅读上面代码我们大概能了解其整个逻辑为:

  1. 如果直接获取共享锁成功则方法结束

  2. 如果获取共享锁失败则将当前线程封装为Node,其nextWaiter标识为SHARED模式,waitStatus为默认值0,追加进同步队列的队尾

  3. 获取当前节点的前置节点,如果前置节点不为头结点则根据前置节点的waitStatus的状态值进行相应的处理

    3.1 前置节点的waitStatus == SIGNAL,表示前置节点获取到锁后会对后置节点进行唤醒,所以直接返回true进行当前节点的中断

    3.2 前置节点的waitStatus > 0,也就是取消状态,队列中剔除该节点,迭代往前寻找直到waitStatus > 0的节点,自身的前置节点

    3.3 前置节点的waitStatus为其他状态,则将其设置为SIGNAL状态

    总之就是前置节点如果会唤醒自己就直接阻塞当前线程,如果不是就该表前置节点的状态使其能后唤醒自己然后对自身进行阻塞

  4. 如果获取的当前节点为头结点且获取共享锁成功,则将自身设置成头结点,如果后置节点也是获取共享锁,则对其进行唤醒,返回结束

2021-09-03_223857.png

共享锁的释放

我们继续看下共享锁的释放

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) { // 判断是否可以释放共享锁
        doReleaseShared(); // 执行共享锁的释放
        return true;
    }
    return false;
}

tryReleaseShared方法由子实现类进行实现,这里我们也先不用过多关注,只需要了解当该方法返回true时代表释放共享锁成功,反之就是失败

private void doReleaseShared() {
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;
                unparkSuccessor(h); // 对后置节点进行唤醒
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;
        }
        if (h == head)
            break;
    }
}

共享锁的释放逻辑就比较简单了,首先判断如果可以释放共享锁,则获取头结点,如果其waitStatus是为SIGNAL,则对后置节点进行唤醒,如果不是则将头结点的waitStatus上设置为PROPAGATE传播模式,无需做其他处理(当前节点的waitStatus不为SIGNAL的话表示后置节点未进行阻塞,所以无需唤醒)

结尾

共享锁的获取与释放基本就是这样了,我们下来结合ReentrantReadWriteLock源码再进行加深了解下

本文通过阅读源码以及根据自身理解所写,如果文中有不正确的地方欢迎指正