• CountDownLatch源码分析


    CountDownLatch源码分析

    CountDowntLatch的作用是让主线程等待所有的子线程执行完毕之后再进行执行,同时它是基于AQS进行实现的,所以它底层肯定是通过自定义AQS共享模式下的同步器来实现的,该同步器需要重写AQS提供的tryAcquireShared()以及tryReleaseShared()方法,只需要告诉AQS是否尝试获取同步资源以及释放同步资源成功即可。

    AQS子类需要定义以及维护同步状态的值,在CountDownLatch中,同步状态state的值为同步资源的个数。


    CountDownLatch的结构

    public class CountDownLatch {
        
        /**
         * 存在一个AQS共享模式下的同步器
         */
        private static final class Sync extends AbstractQueuedSynchronizer {
           // ......
        }
    
        // 存在一个全局的同步器属性
        private final Sync sync;
        
        /**
         * 构建方法初始化同步器,并指定同步资源的个数
         */
        public CountDownLatch(int count) {
            if (count < 0) throw new IllegalArgumentException("count < 0");
            this.sync = new Sync(count);
        }
        
        /**
         * 让主线程进行阻塞
         */
        public void await() throws InterruptedException {
            sync.acquireSharedInterruptibly(1);
        }
    
        public boolean await(long timeout, TimeUnit unit)
            throws InterruptedException {
            return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
        }
        
        /**
         * 让倒数器-1
         */
        public void countDown() {
            sync.releaseShared(1);
        }
    
        public long getCount() {
            return sync.getCount();
        }
    
        public String toString() {
            return super.toString() + "[Count = " + sync.getCount() + "]";
        }
    }
    

    可以看到CountDownLatch中定义了一个同步器,然后存在一个全局的同步器属性,并且通过构建方法来初始化同步器,通过count参数来指定同步资源的个数。

    同时CountDownLatch的await()方法将会直接调用AQS的acquireSharedInterruptibly()方法,countDown()方法直接调用AQS的releaseShared()方法。


    剖析同步器

    private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;
    
        /**
         * 构建方法初始化同步资源的个数
         */
        Sync(int count) {
            setState(count);
        }
    
        /**
         * 获取可用的同步资源个数(就是倒数器当前的值)
         */
        int getCount() {
            return getState();
        }
    
        /**
         * 尝试获取同步资源
         */
        protected int tryAcquireShared(int acquires) {
            // 只有当同步状态的值为0,方法才返回true
            return (getState() == 0) ? 1 : -1;
        }
    
        /**
         * 尝试释放同步资源
         */
        protected boolean tryReleaseShared(int releases) {
            // 死循环保证CAS操作成功
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                // 让同步状态的值-1
                int nextc = c-1;
                // 只有当线程释放同步资源后,同步状态的值0时,该方法才会返回true
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }
    

    tryAcquireShared()方法用于尝试获取同步资源,同时AQS规定,如果获取同步资源成功则返回剩余的可用资源个数,否则返回负数,但在CountDownLatch的同步器的tryAcquireShared()方法中,只有当同步状态的值为0时,才表示获取同步资源成功,方法返回1,否则都为获取同步资源失败,方法返回-1。

    tryReleaseShared()方法用于尝试释放同步资源,同时AQS规定,如果释放同步资源成功,则返回true,否则返回false,一般情况下方法都会返回true(当前同步资源的值 + 要释放的同步资源个数),但是在CountDownLatch的同步器的tryReleaseShared()方法中,并没有累加同步状态的值,而是当线程每次释放同时资源时,都会将同步状态的值-1,只有当线程释放同步资源后,同步状态的值为0时,该方法才会返回true。


    流程总结

    1.首先创建一个CountDownLatch实例,并指定倒数器的阈值。

    2.主线程调用CountDownLatch的await()方法进行阻塞,该方法会直接调用AQS的acquireSharedInterruptibly()方法,acquireSharedInterruptibly()方法又会调用同步器的tryAcquireShared()方法,尝试获取同步资源,tryAcquireShared()方法只有当同步状态的值为0时,才表示获取同步资源成功,方法返回1,由于目前同步状态的值不为0,因此方法返回-1,因此该线程将会封装成Node节点,然后加入到等待队列当中,该线程将会进行阻塞。

    3.子线程调用CountDownLatch的countDown()方法让倒数器-1,该方法会直接调用AQS的releaseShared()方法,releaseShared()方法又会调用同步器的tryReleaseShared()方法,尝试释放同步资源,但是在tryReleaseShared()方法中,每次当线程释放同步资源时,都会将同步状态的值-1,只有当线程释放同步资源后,同步状态的值为0时,该方法才会返回true,否则返回false,如果tryReleaseShared()方法返回false,那么就不做任何处理,只有当该方法返回true时,也就是最后一个子线程执行了countDown()方法,将同步状态的值设置为0,那么就会唤醒离头节点最近的同时等待状态不为CANCELLED的后继节点,也就是主线程,然后主线程调用tryAcquireShared()方法尝试获取同步资源,由于当前同步状态的值已经为0,因此tryAcquireShared()方法返回1,然后主线程直接返回,做自己的事情。


    FAQ

    CountDownLatch为什么不能重用?

    不能重用,因此当主线程被唤醒后,然后调用tryAcquireShared()方法获取了同步资源,然后就直接返回,做自己的事情,永远都不会释放同步资源,因此不能重用。

  • 相关阅读:
    【Oracle】优化器之RBO与CBO
    【Oracle11g】16_数据字典和动态性能视图
    AXI总线介绍
    Verilog 加法器和减法器(8)-串行加法器
    状态机实例-寄存器拷贝
    状态机状态最小化
    vcs+Makefile实现简单的testbench
    quartus II输入原理图及仿真步骤
    Directx11代码下载
    触发器的时序参数与时序分析
  • 原文地址:https://www.cnblogs.com/funyoung/p/13626740.html
Copyright © 2020-2023  润新知