zoukankan      html  css  js  c++  java
  • 等待多线程完成的CountDownLatch(带示例)

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

  • 相关阅读:
    part of Hypertext Transfer Protocol HTTP/1.1
    Run Windows Service as a console program
    UNION 和UNION ALL 的区别
    分布式拒绝服务攻击(DDoS)原理及防范
    执行存储过程超时 SQL
    sql 小技巧 =》本周五和上周四的时间
    OPENXML with xmlns:dt
    Comparing the Timer Classes in the .NET Framework Class Library
    图片(地图)热点区域高亮显示研究
    用YSlow分析我们页面(转)
  • 原文地址:https://www.cnblogs.com/niceyoo/p/13690231.html
Copyright © 2011-2022 走看看