AbstractQueuedSynchronizer(AQS)实现原理(下)- 共享锁

我们在上一节有分析过AQS的一些基础原理及独占锁,接下来继续分析AQS的共享锁,共享锁的获取与独占锁的获取流程大概上是相似的,在接下来的源码分析中会发现这个问题。

获取锁-acquireShared

  1. 共享模式下获取锁,忽略中断(即使中断,线程依然会获取锁)
  2. 源码分析
public class AbstractQueuedSynchronizer {
    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0) // 获取共享锁,有子类实现
            doAcquireShared(arg);
    }
}
复制代码

tryAcquireShared由子类实现并确认最终获取锁的逻辑,在获取锁失败之后,通过doAcquireShared方法以自旋的方式获取锁

public class AbstractQueuedSynchronizer {
    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) { // 前驱节点为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); // 处理线程获取锁失败后的取消
        }
    }
}
复制代码

与不中断独占锁相比,其进入等待队列、自旋、线程挂起的方式是一致的,不一致的是其在自旋过程中获取锁成功时的操作setHeadAndPropagate,此方法 的作用主要是完成当当前线程获取锁成功的出队以及唤醒后继节点(如果存在的话).其中finally的目的是为了在执行doAcquireShared方法的过程中如果出现异 常,取消该线程获取锁的尝试,并从等待队列中清除该线程。

  1. 锁获取过程

image.png

获取锁-acquireSharedInterruptibly

  1. 在共享模式下获取锁,若线程中断则中止
  2. 源码分析
public class AbstractQueuedSynchronizer {
    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted()) // 中断抛出异常
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0) // 获取共享锁
            doAcquireSharedInterruptibly(arg); // 可中断的获取锁
    }
}
复制代码

与获取可中断独占锁的逻辑一致,只是由tryAcquire方法变成了tryAcquireShared

public class AbstractQueuedSynchronizer {
    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);  // 取消该线程获取锁的尝试并从等待队列中删除
        }
    }
}
复制代码

与中断独占锁相比,其进入等待队列、自旋、线程挂起的方式是一致的,不一致的是其在自旋过程中获取锁成功时的操作setHeadAndPropagate,此方法 的作用主要是完成当当前线程获取锁成功的出队以及唤醒后继节点(如果存在的话).其中finally的目的是为了在执行doAcquireShared方法的过程中如果出现异 常,取消该线程获取锁的尝试,并从等待队列中清除该线程。

  1. 锁获取过程

image.png

获取锁-tryAcquireSharedNanos(int arg, long nanosTimeout)

  1. 以独占模式在给定到时间内获取锁,超时失败,如果被中断,锁获取终止
  2. 源码分析
public class AbstractQueuedSynchronizer {
    public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted()) // 中断检查
            throw new InterruptedException();
        return tryAcquireShared(arg) >= 0 || // 尝试获取共享锁
                doAcquireSharedNanos(arg, nanosTimeout); // 在给定的时间内获取锁
    }
}
复制代码

首先判断线程是否中断,中断则抛出异常,同时擦除中断标记;其次通过tryAcquireShared尝试获取锁,成功返回true,失败返回false;最后调用 doAcquireSharedNanos方法在设定到时间内获取锁

public class AbstractQueuedSynchronizer {
    private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (nanosTimeout <= 0L)
            return false;
        final long deadline = System.nanoTime() + nanosTimeout; // 获取锁的最后期限
        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 true;
                    }
                }
                nanosTimeout = deadline - System.nanoTime(); // 剩余时间
                if (nanosTimeout <= 0L) // 小于0,表示已经过了最后期限,返回false
                    return false;
                if (shouldParkAfterFailedAcquire(p, node) && // 是否挂起判断
                        nanosTimeout > spinForTimeoutThreshold) // 剩余时间是否大于自旋时间
                    LockSupport.parkNanos(this, nanosTimeout); // 线程挂起
                if (Thread.interrupted()) // 线程中断判断
                    throw new InterruptedException(); // 抛出异常
            }
        } finally {
            if (failed) // 线程在执行过程中出现异常的后续操作
                cancelAcquire(node); // 取消锁获取并从等待队列中清除该线程
        }
    }
}
复制代码

在进入自旋前获取超时时间、构造当前线程节点并添加到等待队列,然后在进入自旋。如果前驱节点为head,再次尝试获取锁,成功的话当前节点出队并获取锁, 失败则计算剩余等待时间,其次判断是否需要将线程挂起及剩余等待大于自旋时间时,则将线程挂起,否则将在下一次自旋时返回false,同时取消当前线程获取 锁。当线程被挂起后,如果因线程中断被唤醒,则抛出异常,同时取消当前线程获取锁。 3. 锁获取过程

image.png

释放锁-releaseShared

  1. 释放共享锁
  2. 源码分析
public class AbstractQueuedSynchronizer {
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) { // 此方法有子类实现,用于判断是否可以唤醒后继节点
            doReleaseShared(); // 
            return true;
        }
        return false;
    }
}
复制代码

tryReleaseShared用于确认锁是否可释放,可释放的话执行doReleaseShared释放锁

  1. 释放流程

image.png

setHeadAndPropagate-出队并唤醒后继节点分析

public class AbstractQueuedSynchronizer {
    private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        setHead(node); // 设置head,可以理解为出队
        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();
        }
    }
}
复制代码

首先是出队操作,然后根据条件判断是否需要通过doReleaseShared方法唤醒后继节点。

doReleaseShared--释放锁

public class AbstractQueuedSynchronizer {
    private void doReleaseShared() {
        for (;;) { // 循环释放
            Node h = head;
            if (h != null && h != tail) { // 头部节点不为null且头部节点!=尾部节点,意思是等待队列中有线程在等待
                int ws = h.waitStatus; // 获取waitStatus
                if (ws == Node.SIGNAL) { // 如果为-1
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) // 设置头部节点中的waitStatus为0,失败则跳过
                        continue;            // loop to recheck cases
                    unparkSuccessor(h); // 释放锁,唤醒后继节点
                }
                else if (ws == 0 &&
                        !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) // 设置头部节点中的waitStatus为-3,失败则跳过
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }
}
复制代码

与独占锁释放锁不同的是,共享锁在执行一次锁释放操作的时候,将通过循环的方法唤醒所有在等待队中的线程,直到所有线程都被唤醒.

问题与总结

什么是独占锁和共享锁?

  • 独占锁:当一个线程持有锁时,其他线程必须等待锁释放后才能重新去获取锁
  • 共享锁:同一把锁可允许多个线程同时持有

什么是可中断锁和不可中断锁?

  • 可中断锁:一个线程在获取锁或在等待获取锁的过程中如果被中断,将会抛出InterruptedException,并会取消其获取锁的动作,如果在等待队列中,将从等待队列中将其清除
  • 不可中断锁:一个线程在获取锁或在等待获取锁的过程中如果被中断,仍将继续获取锁

AQS中共享锁和独占锁的释放有啥区别?

独占锁:根据FIFO规则,唤醒等待队列中第一个有效的节点 共享锁:从头部节点开始,依次唤醒所有等待节点。

猜你喜欢

转载自juejin.im/post/7049835625624633352
今日推荐