zoukankan      html  css  js  c++  java
  • 倒数闩锁CountDownLatch源码浅析

     

    1 前言

    CountDownLatch是一种同步辅助工具类,它允许一个或多个线程等待,直到在其他线程中执行的一组操作完成为止。(源码分析基于JDK1.8) CountDownLatch需要用给定的闩锁计数count初始化。await方法使当前线程阻塞(每执行一次countDown方法就将闩锁计数减1),直到闩锁计数达到零时(所有因此阻塞等待的线程都)才会被唤醒。CountDownLatch是一次性使用的同步工具,闩锁计数无法重置,如果需要重置计数,可能使用CyclicBarrier更合适。

     

    2 用法示例

    1) 示例1

    我们需要解析一个Excel里多个sheet的数据,此时可以考虑使用多线程,每个线程解析一个sheet里的数据,等到所有的sheet都解析完之后,程序需要提示解析完成。在这个需求中,要实现主线程等待所有线程完成sheet的解析操作,最简单的做法是使用join()方法.

    public class JoinCountDownLatchTest {
        public static void main(String[] args) throws InterruptedException {
            Thread parser1 = new Thread(new Runnable() {
                @Override
                public void run() {
                      System.out.println("parser1 finish");
                }
            });
            Thread parser2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("parser2 finish");
                }
            });
            parser1.start();
            parser2.start();
            parser1.join();
            parser2.join();
            System.out.println("all parser finish");
        }
    }

    CountDownLatch可以实现join类似的功能,但它更强大,它提供了很多API方法,能够实现更精准的控制。

    CountDownLatch的构造方法必须传入一个int类型的参数,这个参数作为闩锁的计数器。

    CountDownLatch的countDownawait方法一般都要配合使用。await方法(休眠)阻塞当前线程,而每调用一次countDown方法,闩锁计数就减1,当其减为0时,当前线程就被唤醒、await方法得以返回。

     class CountDownLatchTest {
        static CountDownLatch c = new CountDownLatch(2);
        public static void main(String[] args) throws InterruptedException {
            Thread parser1 = new Thread(() -> {
                System.out.println("parser1 finish");
                c.countDown();
            });
            Thread parser2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("parser2 finish");
                    c.countDown();
                }
            });
            parser1.start();
            parser2.start();
            c.await();
            System.out.println("all parser finish");
        }
    }

    打印结果:

    parser1 finish

    parser2 finish

    all parser finish

    c.await();被注释掉,就不能保证打印的先后顺序,输出结果如下:

    all parser finish

    parser1 finish

    parser2 finish

    2) 示例2

    这里有两个类Driver和Worker,分别表示驱动者、工作者线程。这里使用了两个CountDownLatch对象,第一个表示启动信号,可防止任何工作者线程Worker前进处理,直到驱动者Driver为它们做好准备为止;第二个表示完成信号,允许驱动者Driver等到所有工作者线程Worker都完成任务为止。

    class Driver { // ...
        void main() throws InterruptedException {
            CountDownLatch startSignal = new CountDownLatch(1);
            CountDownLatch doneSignal = new CountDownLatch(N);
    ​
            for (int i = 0; i < N; ++i) // create and start threads
                new Thread(new Worker(startSignal, doneSignal)).start();
    ​
            doSomethingElse();            // don't let run yet 做准备
            startSignal.countDown();      // let all threads proceed 
            doSomethingElse();
            doneSignal.await();           // wait for all to finish
        }
    }
    ​
    class Worker implements Runnable {
        private final CountDownLatch startSignal;
        private final CountDownLatch doneSignal;
        Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
            this.startSignal = startSignal;
            this.doneSignal = doneSignal;
        }
        public void run() {
            try {
                startSignal.await();
                doWork();
                doneSignal.countDown();
            } catch (InterruptedException ex) {} // return;
        }
    ​
        void doWork() { ... }
    }

    3 实现分析

    CountDownLatch的实现主要基于同步器AbstractQueuedSynchronizer,它利用AQS实现了一个共享锁. CountDownLatch主要有一个Sync类型成员变量sync, Sync是继承抽象类AbstractQueuedSynchronizer的静态内部类。

        private final Sync sync;

    1) 构造方法CountDownLatch(int)

    CountDownLatch的构造方法主要是执行this.sync = new Sync(count)对sync进行实例化, 而Sync(int)又将父类AbstractQueuedSynchronizer的实例变量state设置为指定的count。

    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);//实例化
    }
            Sync(int count) {
                setState(count);//将`AbstractQueuedSynchronizer`的state设为count
            }

    2) 静态内部类Sync

    Sync主要重写了父类的tryAcquireSharedtryReleaseShared方法,这两个方法都是实现共享锁所必须重写的相关方法,其作用分别是尝试获取共享状态、尝试释放共享状态,两者刚好配对。有关AQS详细分析,请看之前的博客AbstractQueuedSynchronizer实现原理分析

    protected int tryAcquireShared(int acquires) {
        //state为0,闩锁计数为0 ,返回1,获取共享状态成功
        //反之闩锁计数不为0,返回-1,获取共享状态失败。
        return (getState() == 0) ? 1 : -1;
    }
    
    protected boolean tryReleaseShared(int releases) {
        // Decrement count; signal when transition to zero
        for (;;) {
            int c = getState();
            if (c == 0)  //state已经为0,非法状态返回false.(只有在已获取锁,即state非零时,才有释放锁的说法)
                return false;
            int nextc = c-1;
            if (compareAndSetState(c, nextc))//cas自旋将state减1
                return nextc == 0;//state自减1后为零,返回true,可释放锁。反之返回false,还不能释放锁。
        }
    }

    另外Sync还提供了一个方法getCount,返回当前剩余的闩锁计数,它直接调用父类AQS的getState实现。

    int getCount() {
        return getState();
    }

    3) await

    await使当前线程休眠等待,直到count减少至0或线程中断。

    await调用了AQS的acquireSharedInterruptibly方法,acquireSharedInterruptibly获取共享锁并响应中断。

    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    await(long , TimeUnit )是await()的超时版本。

    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }

    4) countDown

    countDown将闩锁计数递减1,若递减后为0就将唤醒所有阻塞等待的线程。如果闩锁的计数(递减前)已经为零,就啥也不做,恰好与上面tryReleaseShared方法体中的if (c == 0) return false;所对应。

    public void countDown() {
        sync.releaseShared(1);
    }

    5) getCount

    getCount用于查询当前的闩锁计数

    public long getCount() {
        return sync.getCount();
    }


  • 相关阅读:
    uva10912 Simple Minded Hashing(DP)
    uva10401 Injured Queen Problem(DP)
    uva702 The Vindictive Coach(DP)
    忍者X4将采取自动开通vip,论坛充值淘宝自助购买均可。步骤如下
    C盘不够大,可以这样操作
    任务思维1
    PHP 获取指定日期的星期方法如下
    学学C#开发client,server,C/S架构的程序
    今天的主角就是protobuf-net
    关于忍者站群X4-小飞镖服务器配置帮助汇总。
  • 原文地址:https://www.cnblogs.com/gocode/p/analysis-source-code-of-CountDownLatch.html
Copyright © 2011-2022 走看看