java线程(七)之CountDownLatch

版权声明:本文是作者在学习与工作中的总结与笔记,如有内容是您的原创,请评论留下链接地址,我会在文章开头声明。 https://blog.csdn.net/usagoole/article/details/89789256

CountDownLatch

  1. CountDownLatch是一个同步辅助类。jdk1.5后CountDownLatch也可以实现join功能,并且比join得功能更多
  2. CountDownLatch的构造函数接收一个int类型的参数作为计数器,如果想等待N个点完成就传入N。调用CountDownLatchcountDown()方法,N就减1,调用CountDownLatchawait()方法会阻塞当前线程,直到N变成0。countDown()可以用在任何地方,所以N可以是N个线程,也可以是一个线程里的N个执行步骤。
  3. 用在多线程时,只需要把这个CountDownLatch的引用传递到线程里即可
  4. 计数器必须大于等于0,只是等于0时候,计数器就是零,调用await方法时不会阻塞当前线程。CountDownLatch不可能重新初始化或者修改CountDownLatch对象的内部计数器的值(CountDownLatch计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier)。一个线程调用countDown()方法happen-before另外一个线程调用await()方法

方法

  1. countDown(),当前线程调用此方法,则计数减一
  2. await(), 调用此方法的线程会被挂起,直到计时器的值为0才继续执行。
  3. await(long timeout, TimeUnit unit) 等待一定的时间后count值还没变为0的话就会继续执行

场景

  1. 在实时系统中的使用场景
    • 实现最大的并行性:有时我们想同时启动多个线程,实现最大程度的并行性。例如,我们想测试一个单例类。如果我们创建一个初始计数为1的CountDownLatch,并让所有线程都在这个锁上等待,那么我们可以很轻松地完成测试。我们只需调用一次countDown()方法就可以让所有的等待线程同时恢复执行。
    • 开始执行前等待n个线程完成各自任务:例如应用程序启动类要确保在处理用户请求前,所有N个外部系统已经启动和运行了。(一种典型的场景就是火箭发射。在火箭发射前,为了保证万无一失,往往还要进行各项设备、仪器的检查。 只有等所有检查完毕后,引擎才能点火。这种场景就非常适合使用CountDownLatch)
    • 死锁检测:一个非常方便的使用场景是,你可以使用n个线程访问共享资源,在每次测试阶段的线程数目是不同的,并尝试产生死锁。

案例

  1. 使用CountDownLatch实现join功能

        @Test
        public void countDownLatchJoin() {
            System.out.println("开始主线程" + Thread.currentThread().getName());
            CountDownLatch ct = new CountDownLatch(2);
            new Thread(() -> {
                System.out.println("输入:" + 1);
                //调用计数器减1,countDown可以在任何地方使用
                ct.countDown();
                System.out.println("输入:" + 2);
                ct.countDown();
            }).start();
            try {
                ct.await();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            System.out.println("结束主线程" + Thread.currentThread().getName());
        }
        输出:
            开始主线程main
            输入:1
            输入:2
            结束主线程main
    
  2. 将准备工作分成N个部分,分给N个线程执行,每个线程执行完都调用countDown方法。当所有的子部分完成后,主线程继续执行,其实就是多个线程的join

        @Test
        public void countDownLatchJoinMulitThread() {
            Driver driver = new Driver();
            driver.execute();
        }
    
        static class Driver {
            void execute() {
                int N = 5;
                CountDownLatch doneSignal = new CountDownLatch(N);
                final ExecutorService exec = Executors.newFixedThreadPool(N);
                for (int i = 0; i < N; i++) {
                    exec.execute(new Worker(doneSignal, i));
                }
                //等待所有线程完成处理
                try {
                    doneSignal.await();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                exec.shutdown();
    
            }
        }
    
    
        static class Worker implements Runnable {
            private final CountDownLatch doneSignal;
            private final int i;
    
            public Worker(CountDownLatch doneSignal, int i) {
                this.doneSignal = doneSignal;
                this.i = i;
            }
    
            @Override
            public void run() {
                doWork(i);
                doneSignal.countDown(); // 完成准备工作,更新信号
            }
    
            void doWork(int i) {
                System.out.println("prepare  " + i + "finish");
            }
        }
        输出:
            prepare  0finish
            prepare  1finish
            prepare  2finish
            prepare  3finish
            prepare  4finish
    
  3. 等待所有线程执行完毕。多个CountDownLatch配合使用

        /**
         * 模拟了100米赛跑,10名选手已经准备就绪,只等裁判一声令下。当所有人都到达终点时,比赛结束。
         */
        @Test
        public void countDownLatchTest() {
            // 开始的倒数锁
            final CountDownLatch begin = new CountDownLatch(1);
            // 结束的倒数锁
            final CountDownLatch end = new CountDownLatch(10);
            // 十名选手
            final ExecutorService exec = Executors.newFixedThreadPool(10);
            for (int index = 0; index < 10; index++) {
                final int NO = index + 1;
                Runnable run = new Runnable() {
                    @Override
                    public void run() {
                        try {
                            // 如果当前计数为零,则此方法立即返回。
                            begin.await();
                            Thread.sleep((long) (Math.random() * 10000));
                            System.out.println("线程:" + Thread.currentThread().getName() + "--No." + NO + " arrived");
                        } catch (InterruptedException e) {
                            Thread.currentThread().interrupt();
                        } finally {
                            // 每个选手到达终点时,end就减一
                            end.countDown();
                        }
                    }
                };
                exec.submit(run);
            }
            System.out.println("Game Start,此时开始计数器是" + begin.getCount() + ",结束计数器是" + end.getCount());
            // begin减一,开始游戏
            begin.countDown();
            // 等待end变为0,即所有选手到达终点
            try {
                end.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Game Over,此时开始计数器是" + begin.getCount() + ",结束计数器是" + end.getCount());
            exec.shutdown();
        }
        输出:
            Game Start,此时开始计数器是1,结束计数器是10
            线程:pool-1-thread-2--No.2 arrived
            线程:pool-1-thread-1--No.1 arrived
            线程:pool-1-thread-6--No.6 arrived
            线程:pool-1-thread-3--No.3 arrived
            线程:pool-1-thread-10--No.10 arrived
            线程:pool-1-thread-9--No.9 arrived
            线程:pool-1-thread-8--No.8 arrived
            线程:pool-1-thread-7--No.7 arrived
            线程:pool-1-thread-5--No.5 arrived
            线程:pool-1-thread-4--No.4 arrived
            Game Over,此时开始计数器是0,结束计数器是0
    
    
  4. 等待所有线程执行完毕。多个CountDownLatch配合使用

        @Test
        public void countDownLatchTest2() {
            Driver driver = new Driver();
            driver.execute();
        }
    
        static class Driver {
            void execute() {
                int N = 3;
                CountDownLatch startSignal = new CountDownLatch(1);
                CountDownLatch doneSignal = new CountDownLatch(N);
                final ExecutorService exec = Executors.newFixedThreadPool(N);
    
                for (int i = 0; i < N; i++) {
                    exec.submit(new Worker(startSignal, doneSignal));
                }
                //释放所有线程的等待
                startSignal.countDown();
                //等待所有线程完成处理
                try {
                    doneSignal.await();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
    
                exec.shutdown();
            }
        }
    
    
        static class Worker implements Runnable {
            private final CountDownLatch startSignal;
            private final CountDownLatch doneSignal;
    
            Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
                this.startSignal = startSignal;
                this.doneSignal = doneSignal;
            }
    
            @Override
            public void run() {
                try {
                    startSignal.await();    // 等待开启信号
                    doWork();
                } catch (InterruptedException ex) {
                    Thread.currentThread().interrupt();
                } finally {
                    doneSignal.countDown(); // 执行完成后更新完成信号
                }
    
            }
    
            void doWork() {
                System.out.println("thread:" + Thread.currentThread().getName() + " work");
            }
        }
    
    

猜你喜欢

转载自blog.csdn.net/usagoole/article/details/89789256