开始磨刀霍霍向多线程了,这期是 CountDownLatch 的一个小示例。

定义:CountDownLatch 允许一个或多个线程等待其他线程完成操作。

应用需求举例:假设有4个线程,A、B、C、D,线程 D 需要在 A、B、C 执行完之后再执行。

应用需求分析:如上描述,如果想让线程 D 最后执行,结合之前的学习,我们可以采用 join() 方法来实现,比如在 A 线程调 B.join(),B 线程调用 C.join() 等等,我们来回忆一下 join() 方法:一个线程调用另一个线程的 join() 方法时,当前线程阻塞,等待被调用 join() 方法的线程执行完毕才能继续执行,这样可以保证线程执行顺序。

如下为 join() 方法实现:

public class Demo {
    public static void main(String[] args) {
        Thread A = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("A");
            }
        });
        Thread B = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("B 开始等待 A");
                try {
                    A.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("B");
            }
        });
        Thread C = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("C 开始等待 B");
                try {
                    B.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("C");
            }
        });
        Thread D = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("D 开始等待 C");
                try {
                    C.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("D");
            }
        });
        B.start();
        A.start();
        C.start();
        D.start();
    }
}

执行结果如下:

B 开始等待 A
C 开始等待 B
A
D 开始等待 C
B
C
D

显然 join() 方法是可以完成 D 线程最后执行的要求,但是却失去了多线程并行执行的意义,怎么讲?

因为线程 A、B、C 无法完成同步运行,本质上还是串行,join() 方法其内部的实现是基于等待通知机制。

为了解决这个问题,我们可以利用 CountdownLatch 来实现这类通信方式,用法如下:

  1. CountdownLatch 构造函数接收一个 int 类型的参数作为计数器,如果想等待 N 个点,就传入 N
  2. 调用 CountdownLatch 的 countDown() 方法时,计数器 N 就会减 1
  3. 在等待线程中调用 await() 方法,会进入等待状态,直到计数器 N 变为 0 后再执行;

CountdownLatch 方式实现:

public class C {
    public static void main(String[] args) {
        int N = 3;
        CountDownLatch countDownLatch = new CountDownLatch(N);
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("D 等待执行中...");
                try {
                    countDownLatch.await();
                    System.out.println("D 开始执行");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        for (char threadName='A'; threadName <= 'C'; threadName++) {
            final String tN = String.valueOf(threadName);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(tN + " 正在执行");
                    try {
                        Thread.sleep(100);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println(tN + " 执行完毕");
                    countDownLatch.countDown();
                }
            }).start();
        }
    }
}

执行结果如下:

D 等待执行中...
B 正在执行
A 正在执行
C 正在执行
C 执行完毕
A 执行完毕
B 执行完毕
D 开始执行

应用场景模拟:导出用户账单明细,按年月日三个维度进行计算并显示,年月日分别放入 3 个 Excel 中,最终将Excel 合并为一个 zip 压缩文件返回给用户。

public class Demo2 {

    public static void main(String[] args) {

        try {
            // 模拟跟路径,根据自己路径修改
            String tempDir = "/C/workspace/File/excel";
            // 模拟存放文件路径
            String baseFileName = "202009171016";
            String zipFileName = tempDir + "/" + baseFileName + ".zip";
            int N = 3;
            CountDownLatch mDoneSignal = new CountDownLatch(N);
            ExecutorService executor = Executors.newFixedThreadPool(N);
            // 用户盛放 excel 路径
            List<String> excelFileNames = new ArrayList<String>();

            for (int i = 0; i < 3; i++) {
                File file = new File(tempDir + "/" + baseFileName + "_" + i + ".xls");
                file.createNewFile();
                excelFileNames.add(tempDir + "/" + baseFileName + "_" + i + ".xls");
                executor.execute(new MyThread(file, i, mDoneSignal));
            }

            // 关闭启动线程
            executor.shutdown();
            // 等待子线程结束,再继续执行下面的代码
            executor.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
            String[] fileNames = excelFileNames.toArray(new String[excelFileNames.size()]);
            try {
                mDoneSignal.await();// 等待所有工作线程结束
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("所有数据处理完毕...");
            System.out.println("开始将数据打包zip...");
        }catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 模拟线程
     */
    public static class MyThread implements Runnable{

        private final File file;
        private final CountDownLatch mDoneSignal;
        private final int index;

        public MyThread(File file, int index, CountDownLatch mDoneSignal) {
            this.index = index;
            this.file = file;
            this.mDoneSignal = mDoneSignal;
        }

        @Override
        public void run() {
            // 线程执行逻辑,处理 工作簿 等
            switch (index){
                case 0:
                    System.out.println("线程A执行完毕:" + LocalTime.now());
                    break;
                case 1:
                    System.out.println("线程B执行完毕:" + LocalTime.now());
                    break;
                case 2:
                    System.out.println("线程C执行完毕:" + LocalTime.now());
                    break;
            }
            // 线程完成,计数器减一
            mDoneSignal.countDown();
        }
    }

}

执行结果如下:

线程C执行完毕:10:38:01.454
线程B执行完毕:10:38:01.455
线程A执行完毕:10:38:01.455
所有数据处理完毕...
开始将数据打包zip...

博客持续更新,关注订阅,未来,我们一起成长。

本文首发于博客园:https://www.cnblogs.com/niceyoo/p/13690231.html

等待多线程完成的CountDownLatch(带示例)的更多相关文章

  1. Java并发工具类(一):等待多线程完成的CountDownLatch

    作用 CountDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行 简介 CountDownLatch是在java1.5被引入的,存在于java.uti ...

  2. 并发工具类(一)等待多线程的CountDownLatch

    前言   JDK中为了处理线程之间的同步问题,除了提供锁机制之外,还提供了几个非常有用的并发工具类:CountDownLatch.CyclicBarrier.Semphore.Exchanger.Ph ...

  3. Java多线程系列——计数器 CountDownLatch

    简介: CountDownLatch 是一个非常实用的多线程控制工具类,通常用来控制线程的等待,它可以让某个线程等待直到倒计时结束 CountDownLatch 提供了两个主要的方法,await(). ...

  4. CyclicBarrier 是如何做到等待多线程到达一起执行的?

    我们有些场景,是需要使用 多线各一起执行某些操作的,比如进行并发测试,比如进行多线程数据汇总. 自然,我们可以使用 CountDownLatch, CyclicBarrier, 以及多个 Thread ...

  5. java多线程系列(八)---CountDownLatch和CyclicBarrie

    CountDownLatch 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 java多线 ...

  6. Java多线程之---用 CountDownLatch 说明 AQS 的实现原理

    本文基于 jdk 1.8 . CountDownLatch 的使用 前面的文章中说到了 volatile 以及用 volatile 来实现自旋锁,例如 java.util.concurrent.ato ...

  7. java多线程---------java.util.concurrent并发包----------等待多线程完成

    一.等待多线程完成的join的使用.CoundownLantch.CyclicBarrier .

  8. 【转】oozie安装和自带示例的使用

    oozie安装 [转]http://www.tuicool.com/articles/qUVNJn oozie自带示例的使用 [转]http://blog.csdn.net/zhu_xun/artic ...

  9. java高并发系列 - 第16天:JUC中等待多线程完成的工具类CountDownLatch,必备技能

    这是java高并发系列第16篇文章. 本篇内容 介绍CountDownLatch及使用场景 提供几个示例介绍CountDownLatch的使用 手写一个并行处理任务的工具类 假如有这样一个需求,当我们 ...

随机推荐

  1. Flink的DataSource三部曲之一:直接API

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  2. Netty源码解析 -- ChannelOutboundBuffer实现与Flush过程

    前面文章说了,ChannelHandlerContext#write只是将数据缓存到ChannelOutboundBuffer,等到ChannelHandlerContext#flush时,再将Cha ...

  3. C# 字符串处理类

    using System;using System.Collections.Generic;using System.Text;using System.Text.RegularExpressions ...

  4. Unity正交相机智能包围物体(组)方案

    Unity正交相机智能包围物体(组)方案 目录 Unity正交相机智能包围物体(组)方案 一.技术背景 二.相关概念 2.1 正交摄像机 2.2 正交相机的Size 2.3 相机的Aspect 2.4 ...

  5. 这么好?中科图新项目经理教你开发LocaSpace功能

    LocaSpace是专注于实景三维数据应用的三维数字地球软件,为开发者提供强大.稳定的SDK服务,花费很少的精力即可在自己产品中集成某项功能.   我们将于2018年7月18日至7月20日举办&quo ...

  6. SpringBoot进阶教程(六十四)注解大全

    在Spring1.x时代,还没出现注解,需要大量xml配置文件并在内部编写大量bean标签.Java5推出新特性annotation,为spring的更新奠定了基础.从Spring 2.X开始spri ...

  7. 使用jQuery简单实现返回顶部的一个小案例

    1.简单写一个页面 首先我们应该创建两个盒子,container盒子主要模拟页面滚动到的位置,back盒子主要功能是实现返回顶部的功能 2.简单的对这两个盒子写一些样式 我们应该先将返回顶部盒子隐藏( ...

  8. Effective Modern C++ ——条款2 条款3 理解auto型别推导与理解decltype

    条款2.理解auto型别推导 对于auto的型别推导而言,其中大部分情况和模板型别推导是一模一样的.只有一种特例情况. 我们先针对auto和模板型别推导一致的情况进行讨论: //某变量采用auto来声 ...

  9. ASCII、Unicode、UTF-8、UTF-8(without BOM)、UTF-16、UTF-32傻傻分不清

    ASCII.Unicode.UTF-8.UTF-8(without BOM).UTF-16.UTF-32傻傻分不清 目录 ASCII.Unicode.UTF-8.UTF-8(without BOM). ...

  10. APP分享多张图片到微信和朋友圈

    产品需求: 微信分享多图至好友,朋友圈.由于微信禁用了分享9图至朋友圈功能,这里分享微信只是将图片保存至本地,具体让用户手动分享. 问题分析: 微信没有提供分享多图的SDK,因此我们实现调用系统自带的 ...