AQS 之 共享锁 源码剖析

AQS 之 共享锁 源码剖析

上一篇我们详细讲解了互斥锁的源码实现,也即同一时刻只有一个线程获得锁,其它参与竞争的线程必须等待。有一种情况是允许多个线程同时获得锁,如读写锁(ReentrantReadWriteLock)允许所有读线程同时获取锁,写线程阻塞,也即读读共享,读写互斥,写写互斥。那么接下来我们就来看看共享锁的源码实现。

获取锁原理

acquireShared 方法原理

获取共享锁,该方法不响应中断,如果希望它响应中断请调用 acquireSharedInterruptibly 方法。acquireShared 方法原理同 acquire 原理一样,通过调用 tryAcquireShared 方法获取锁(子类实现),获取锁失败将进一步调用 doAcquireShared 方法

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}
doAcquireShared 方法原理

实现流程同 acquireQueued 方法,读者可以对比着学习。

1、包装 Node 节点,将其加入竞争队列,注意此处节点模式为 SHARED 模式

2、获取当前节点的 prev 节点,判断是否是头结点,是,尝试获取一次锁,获取锁成功调用 setHeadAndPropagate 方法,更新头结点并唤醒后续 SHARED 节点

3、再次尝试获取锁失败,则进入 OS 阻塞等待

private void doAcquireShared(int arg) {
    // 以 SHARED 模式添加到竞争队列,addWaiter 方法详解请看上一篇的【互斥锁源码分析】
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            // 获取 prev 节点信息
            final Node p = node.predecessor();
            if (p == head) {
                // 是头结点,尝试获取锁
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    // 获取锁成功,更新头结点,如果当前是 SHARED 模式,那么继续唤醒后续 SHARED 模式的节点
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted) // 如果发生中断,重新设置中断标志位
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            // shouldParkAfterFailedAcquire 与 parkAndCheckInterrupt 方法已在上篇【互斥锁源码分析】中详细讲解过,此处不做过多赘述
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; // 保存当前执行时的头结点 head
    setHead(node);// 更新头结点
    // propagate 为 tryAcquireShared 方法的返回值,大于 0 表明获取到了锁,第一个 h.waitStatus < 0 是当前线程执行时的头结点,第二个 h.waitStatus 是更新后的头结点状态,也即当前节点的状态
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;
        if (s == null || s.isShared())// 当前节点的 next 节点如果是 SHARED 模式,继续唤醒
            doReleaseShared();
    }
}
acquireSharedInterruptibly 方法原理

可响应中断的方式获取锁

public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    // 如果线程被中断,直接抛出中断异常
    if (Thread.interrupted()) // currentThread().isInterrupted(true)
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0) // 获取锁失败 调用 doAcquireSharedInterruptibly 以响应中断的方式加入竞争队列等待
        doAcquireSharedInterruptibly(arg);
}
doAcquireSharedInterruptibly方法原理

支持响应中断的方式被唤醒,其他逻辑同 doAcquireShared 方法

private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException(); // 被中断时 抛出中断异常
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

释放锁原理

releaseShared 方法原理

释放共享锁,释放成功调用 doReleaseShared 唤醒后续 SHARED 等待节点

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) { // 子类实现具体释放逻辑
        doReleaseShared();
        return true;
    }
    return false;
}
doReleaseShared 方法原理

共享锁释放成功后,该线程将负责唤醒其 next 节点(SHARED 节点),在多线程并发条件下,该线程可能还需要帮助唤醒其他的 SHARED 等待节点(当看到 Semaphore 信号量的实现时你将会明白 PROPAGATE 的用处)

private void doReleaseShared() {
    for (;;) {
        Node h = head; // 保存当前头结点,保证后续操作的一致性
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) { // 将 状态更新为 0 
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;           // CAS 失败,重复执行
                unparkSuccessor(h);// 唤醒 next 节点
            }
            else if (ws == 0 && // 多线程并发同时释放了锁,如 Semaphore 信号量,此时 CAS 更新 节点状态为 PROPAGATE
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head) // 头结点未发生变化 直接退出循环,如果头结点发生了变更,将要重复循环,帮忙唤醒后续需要唤醒的节点
            break;
    }
}

你可能感兴趣的:(【JAVA】JUC,之,AQS,与,锁实现篇,java,jvm,后端)