05.JUC 锁 - AQS - 独占模式

基本概念

AQS 的独占模式,表示 AQS 通过独占模式获取/释放锁。该类对应的方法为 acquire /acquireInterruptibly/tryAcquireNanos、release

在 AQS 中共有独占、共享两种模式。

acquire

acquire 表示以独占模式获取锁对象,并忽略中断

public final void acquire(int arg) {
    if (!tryAcquire(arg) && 
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
        // 设置线程当前线程中断标记位
        selfInterrupt();
    }
}

private static void selfInterrupt() {
    // 只修改线程的中断标记位,线程并不会真正中断,所以又称忽略中断
    Thread.currentThread().interrupt();
}

从代码中可以知道,获取锁对象的完整操作如下:

  • tryAcquire,表示尝试获取锁,该方法是空方法,留给 AQS 的子类实现
  • acquireQueued,表示获取锁失败时则将当前线程包装成节点再添加进等待队列

1.acquireQueued

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;

        // 进入自旋状态
        for (;;) {
            // 判断当前节点是否是等待队列的[头节点的后继节点]
            // 若是,再次尝试获取锁(这里之所以这么做,是为了提高线程获得锁的概述)
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                // 成功获取锁,执行出队操作
                setHead(node);
                p.next = null; 
                failed = false;
                return interrupted;
            }

            // 尝试获取失败后的操作
            if (shouldParkAfterFailedAcquire(p, node) && 
                parkAndCheckInterrupt()){
                interrupted = true;
            }   
        }
    } finally {
        if (failed){
            cancelAcquire(node);
        }
    }
}

从上面的代码可知,一旦当前线程进入自旋状态,除非成功获取锁,否则将一直自旋下去。再来看看它获取锁失败后的两个操作。

2.shouldParkAfterFailedAcquire

shouldParkAfterFailedAcquire,该方法用于判断 node 前节点的状态,若不是 SIGNAL 状态,则会将其修改成该状态再返回 false。由于 for 循环的缘故,在成功获取锁之前,该方法会一直被调用,直至前节点的状态被修改成 SIGNAL 。

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL){
        return true;
    }

    if (ws > 0) {
        // 移除 node 前面等待状态为 CANCELLED 的节点
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        // 修改 node 前节点的等待状态
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

3.parkAndCheckInterrupt

进入该方法之后线程会进入阻塞。

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

acquire 整体过程如下图所示:

《05.JUC 锁 - AQS - 独占模式》

acquireInterruptibly

acquireInterruptibly 表示以独占模式获取锁对象,不忽略中断

public final void acquireInterruptibly(int arg) throws InterruptedException {
    if (Thread.interrupted()){
        // 抛出异常...
    }
    if (!tryAcquire(arg)){
        doAcquireInterruptibly(arg);
    }   
}

private void doAcquireInterruptibly(int arg) throws InterruptedException {
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            // 节点出队操作
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; 
                failed = false;
                return;
            }
            if (shouldParkAfterFailedAcquire(p, node) && 
                parkAndCheckInterrupt()){
                // 关键 -> 这里不忽略中断,直接抛出异常中断线程
                throw new InterruptedException();
            }
        }
    } finally {
        if (failed){
            cancelAcquire(node);
        }   
    }
}

tryAcquireNanos

tryAcquireNanos 表示以独占模式获取锁对象,尝试获取失败,则线程进入阻塞状态一段时间。线程在进入阻塞状态后有两种情况:

  • 进入阻塞状态,一直到 nanosTimeout 超时,返回 false;
  • 进入阻塞状态,时长未到 nanosTimeout 被唤醒,则通过自旋再次尝试,成功获取锁或直到超时返回结果。

下面来看它的实现过程:

public final boolean tryAcquireNanos(int arg, long nanosTimeout) 
    throws InterruptedException {
    if (Thread.interrupted()){
        // 抛出异常...
    }
    return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout);
}

与上面两个获取锁对象的步骤基本一致,区别在 doAcquireNanos 这个方法:

static final long spinForTimeoutThreshold = 1000L;

private boolean doAcquireNanos(int arg, long nanosTimeout) 
    throws InterruptedException {
    // 获取当前时间
    long lastTime = System.nanoTime();

    // 将节点添加进等待队列
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        // 进入自旋状态
        for (;;) {
            // 成功获取锁则执行出队操作
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; 
                failed = false;
                return true;
            }

            if (nanosTimeout <= 0){
                return false;
            }

            if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold){
                // 线程进入阻塞状态一段时间 
                LockSupport.parkNanos(this, nanosTimeout);
            }

            // 计算尝试剩余时间 
            long now = System.nanoTime();
            nanosTimeout -= now - lastTime;

            lastTime = now;
            if (Thread.interrupted()){
                // 抛出异常
            }

        }
    } finally {
        if (failed){
            cancelAcquire(node);
        }
    }
}

release

该操作表示通过独占模式释放锁对象。

public final boolean release(int arg) {
    // ①尝试释放锁,空方法留给 AQS 的子类实现
    if (tryRelease(arg)) {
        Node h = head;
        // 判断头节点不为空且节点状态不为 0 
        if (h != null && h.waitStatus != 0){
            // ②关键 ->唤醒同步等待队列中的[头节点的下一个节点]
            unparkSuccessor(h);
        }
        return true;
    }
    return false;
}

关键来看 unparkSuccessor 这个方法,入参节点一般为等待队列的头节点 head

private void unparkSuccessor(Node node) {
    // 修改节点的等待状态
    int ws = node.waitStatus;
    if (ws < 0){
        compareAndSetWaitStatus(node, ws, 0);
    }

    // 剔除队列中已经被置为空或者状态为 CANCELLED 的节点
    // 通过判断节点的后继节点是否为空,为空则继续往后寻找来实现
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev){
            if (t.waitStatus <= 0){
                s = t;
            }
        }
    }

    // 不为空,则通过 unpark 取消阻塞
    if (s != null){
        LockSupport.unpark(s.thread);
    }
}

需要注意的是 release 释放锁对象后,会唤醒通过 acquire 进入等待队列的线程,然后由于循环的缘故再次尝试获取锁,成功则执行出队操作。

    原文作者:JUC
    原文地址: https://blog.csdn.net/u012420654/article/details/56488373
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞