Java教程

【死磕 Java 并发】—– J.U.C 之 AQS:同步状态的获取与释放

本文主要是介绍【死磕 Java 并发】—– J.U.C 之 AQS:同步状态的获取与释放,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

摘要: 原创出处 http://cmsblogs.com/?p=2197 「小明哥」欢迎转载,保留摘要,谢谢!

在前面提到过,AQS 是构建 Java 同步组件的基础,我们期待它能够成为实现大部分同步需求的基础。

AQS 的设计模式采用的模板方法模式,子类通过继承的方式,实现它的抽象方法来管理同步状态。对于子类而言,它并没有太多的活要做,AQS 已经提供了大量的模板方法来实现同步,主要是分为三类:

  • 独占式获取和释放同步状态

  • 共享式获取和释放同步状态

  • 查询同步队列中的等待线程情况。

自定义子类使用 AQS 提供的模板方法,就可以实现自己的同步语义

1. 独占式

独占式,同一时刻,仅有一个线程持有同步状态

1.1 独占式同步状态获取

#acquire(int arg) 方法,为 AQS 提供的模板方法。该方法为独占式获取同步状态,但是该方法对中断不敏感。也就是说,由于线程获取同步状态失败而加入到 CLH 同步队列中,后续对该线程进行中断操作时,线程不会从 CLH 同步队列中移除。代码如下

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

第 2 行:调用 #tryAcquire(int arg) 方法,去尝试获取同步状态,获取成功则设置锁状态并返回 true ,否则获取失败,返回 false 。若获取成功,#acquire(int arg) 方法,直接返回,不用线程阻塞,自旋直到获得同步状态成功。

#tryAcquire(int arg) 方法,需要自定义同步组件自己实现,该方法必须要保证线程安全的获取同步状态。代码如下:

protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

直接抛出 UnsupportedOperationException 异常。

第 3 行:如果 #tryAcquire(int arg) 方法返回 false ,即获取同步状态失败,则调用 #addWaiter(Node mode) 方法,将当前线程加入到 CLH 同步队列尾部。并且, mode 方法参数为 Node.EXCLUSIVE ,表示独占模式。

第 3 行:调用 boolean #acquireQueued(Node node, int arg) 方法,自旋直到获得同步状态成功。详细解析,见 「1.1.1 acquireQueued」 中。另外,该方法的返回值类型为 boolean ,当返回 true 时,表示在这个过程中,发生过线程中断。但是呢,这个方法又会清理线程中断的标识,所以在种情况下,需要调用【第 4 行】的 #selfInterrupt() 方法,恢复线程中断的标识,代码如下:

static void selfInterrupt() {
    Thread.currentThread().interrupt();
}
1.1.1 acquireQueued

boolean #acquireQueued(Node node, int arg) 方法,为一个自旋的过程,也就是说,当前线程(Node)进入同步队列后,就会进入一个自旋的过程,每个节点都会自省地观察,当条件满足,获取到同步状态后,就可以从这个自旋过程中退出,否则会一直执行下去。

流程图如下:

image

代码如下:

 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; // help GC
                    failed = false;
                    return interrupted;
                }
                 // 获取失败,线程等待--具体后面介绍
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            // 获取同步状态发生异常,取消获取。
            if (failed)
                cancelAcquire(node);
        }
    }
1.1.2 shouldParkAfterFailedAcquire
 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
         // 获得前一个节点的等待状态
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
  • 第 4 至 9 行:等待状态为 Node.SIGNAL 时,表示 pred 的下一个节点 node 的线程需要阻塞等待。在 pred 的线程释放同步状态时,会对 node 的线程进行唤醒通知。所以,【第 9 行】返回 true ,表明当前线程可以被 park安全的阻塞等待。
这篇关于【死磕 Java 并发】—– J.U.C 之 AQS:同步状态的获取与释放的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!