开始磨刀霍霍向多线程了,这期是 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

最新文章

  1. myeclipse转到函数定义的方法去
  2. CSS设置透明的两种方式
  3. zz转码问题
  4. 在centos上编译安装mariadb数据库
  5. ubuntu 14.04 键盘快捷键显示
  6. Linux:软件安装
  7. js 日期有效性验证 的一点思考
  8. WF4 常用类&lt;第二篇&gt;
  9. jquery移除、绑定、触发元素事件使用示例详解
  10. Ext实现简单计算器
  11. Java Swing paint repaint update 方法的关系
  12. eclipse下SpringMVC+Maven+Mybatis+MySQL项目搭建
  13. excel计算时间差值
  14. MySQL性能分析工具之PROFILE
  15. c# 添加注册表
  16. as3中xml文件的加载和读取
  17. datagridview 纵向 横向 合并单元格
  18. zoj3662Math Magic
  19. 如何在linux系统内用openssl 生成 过期的证书
  20. Total Commander的初次体验

热门文章

  1. LWJGL3的内存管理
  2. 《Web接口开发与自动化测试》学习笔记(三)
  3. 服务网格istio概念应知应会
  4. 3、Django之路由层
  5. pip升级失败
  6. 【阿里云-大数据】阿里云DataWorks学习视频汇总
  7. Numpy_01
  8. TCP特点
  9. 【webSokect】基于django Channels的简单实现
  10. git , repo out off memory