Java并发利器CountDownLatch深度解析与实战应用小结

 更新时间:2025年06月18日 08:49:12   作者:一只划水的程序猿  
CountDownLatch就像一个倒计时器,当所有任务完成后,主线程才继续执行,本文将通过简单易懂的方式,带你掌握这个强大的并发工具,需要的朋友可以参考下

Java并发利器:CountDownLatch深度解析与实战应用

多线程编程中,让主线程等待所有子任务完成是个常见需求。CountDownLatch就像一个倒计时器,当所有任务完成后,主线程才继续执行。本文将通过简单易懂的方式,带你掌握这个强大的并发工具。

一、CountDownLatch是什么?

1. 基本概念

CountDownLatch就是一个"倒计数门闩":

  • 倒计数:从指定数字开始递减到0
  • 门闩:当计数为0时,门闩打开,等待的线程继续执行
  • 一次性:用完即弃,不能重置

2. 基本用法

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        // 创建计数器,初始值为3
        CountDownLatch latch = new CountDownLatch(3);
        // 启动3个任务
        for (int i = 0; i < 3; i++) {
            final int taskId = i;
            new Thread(() -> {
                System.out.println("任务" + taskId + "开始执行");
                try {
                    Thread.sleep(2000); // 模拟任务执行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任务" + taskId + "执行完成");
                latch.countDown(); // 计数器减1
            }).start();
        }
        System.out.println("主线程等待所有任务完成...");
        latch.await(); // 等待计数器变为0
        System.out.println("所有任务完成,主线程继续执行");
    }
}

运行结果:

主线程等待所有任务完成...
任务0开始执行
任务1开始执行
任务2开始执行
任务0执行完成
任务1执行完成
任务2执行完成
所有任务完成,主线程继续执行

二、核心API介绍

CountDownLatch只有4个关键方法:

public class CountDownLatchAPI {
    public void demonstrateAPI() throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3);
        // 1. countDown() - 计数器减1
        latch.countDown();
        // 2. await() - 等待计数器变为0
        latch.await();
        // 3. await(时间, 单位) - 超时等待
        boolean finished = latch.await(5, TimeUnit.SECONDS);
        // 4. getCount() - 获取当前计数值
        long count = latch.getCount();
        System.out.println("剩余计数: " + count);
    }
}

三、经典应用场景

场景1:等待多个任务完成

最常用的场景,主线程等待所有子任务完成:

public class WaitMultipleTasksDemo {
    // 模拟订单处理:需要等待库存检查、用户验证、支付验证都完成
    public void processOrder(String orderId) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3);
        // 库存检查
        new Thread(() -> {
            try {
                System.out.println("开始库存检查...");
                Thread.sleep(1000);
                System.out.println("库存检查完成");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                latch.countDown();
            }
        }).start();
        // 用户验证
        new Thread(() -> {
            try {
                System.out.println("开始用户验证...");
                Thread.sleep(1500);
                System.out.println("用户验证完成");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                latch.countDown();
            }
        }).start();
        // 支付验证
        new Thread(() -> {
            try {
                System.out.println("开始支付验证...");
                Thread.sleep(800);
                System.out.println("支付验证完成");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                latch.countDown();
            }
        }).start();
        System.out.println("等待所有验证完成...");
        latch.await();
        System.out.println("订单处理完成: " + orderId);
    }
}

场景2:控制并发启动

让多个线程同时开始执行:

public class ConcurrentStartDemo {
    // 模拟赛跑:所有选手同时起跑
    public void startRace() throws InterruptedException {
        int runnerCount = 5;
        CountDownLatch startGun = new CountDownLatch(1); // 发令枪
        CountDownLatch finish = new CountDownLatch(runnerCount); // 终点线
        // 创建选手
        for (int i = 0; i < runnerCount; i++) {
            final int runnerId = i;
            new Thread(() -> {
                try {
                    System.out.println("选手" + runnerId + "准备就绪");
                    startGun.await(); // 等待发令枪
                    // 开始跑步
                    System.out.println("选手" + runnerId + "开始跑步");
                    Thread.sleep(new Random().nextInt(3000)); // 模拟跑步时间
                    System.out.println("选手" + runnerId + "到达终点");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    finish.countDown();
                }
            }).start();
        }
        Thread.sleep(2000); // 等待选手准备
        System.out.println("预备...开始!");
        startGun.countDown(); // 发令
        finish.await(); // 等待所有选手完成
        System.out.println("比赛结束!");
    }
}

场景3:分段计算

将大任务拆分成小任务并行计算:

public class ParallelCalculationDemo {
    // 并行计算数组的和
    public long calculateSum(int[] array) throws InterruptedException {
        int threadCount = 4;
        CountDownLatch latch = new CountDownLatch(threadCount);
        AtomicLong totalSum = new AtomicLong(0);
        int chunkSize = array.length / threadCount;
        for (int i = 0; i < threadCount; i++) {
            final int start = i * chunkSize;
            final int end = (i == threadCount - 1) ? array.length : (i + 1) * chunkSize;
            new Thread(() -> {
                long partialSum = 0;
                for (int j = start; j < end; j++) {
                    partialSum += array[j];
                }
                totalSum.addAndGet(partialSum);
                System.out.println("线程计算范围[" + start + "," + end + "),结果:" + partialSum);
                latch.countDown();
            }).start();
        }
        latch.await();
        return totalSum.get();
    }
    public static void main(String[] args) throws InterruptedException {
        int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        ParallelCalculationDemo demo = new ParallelCalculationDemo();
        long result = demo.calculateSum(array);
        System.out.println("总和:" + result);
    }
}

四、使用注意事项

1. 异常处理要点

核心原则:无论是否异常,都要调用countDown()

// ✅ 正确写法
new Thread(() -> {
    try {
        // 业务逻辑
        doSomething();
    } catch (Exception e) {
        System.err.println("任务异常:" + e.getMessage());
    } finally {
        latch.countDown(); // 确保在finally中调用
    }
}).start();
// ❌ 错误写法
new Thread(() -> {
    try {
        doSomething();
        latch.countDown(); // 异常时不会执行,导致死锁
    } catch (Exception e) {
        System.err.println("任务异常:" + e.getMessage());
        // 忘记调用countDown()
    }
}).start();

2. 避免无限等待

// 设置超时时间,避免无限等待
boolean finished = latch.await(10, TimeUnit.SECONDS);
if (finished) {
    System.out.println("所有任务完成");
} else {
    System.out.println("等待超时,可能有任务失败");
}

3. 合理使用线程池

public void useWithThreadPool() throws InterruptedException {
    CountDownLatch latch = new CountDownLatch(5);
    ExecutorService executor = Executors.newFixedThreadPool(3);
    for (int i = 0; i < 5; i++) {
        final int taskId = i;
        executor.submit(() -> {
            try {
                System.out.println("执行任务" + taskId);
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                latch.countDown();
            }
        });
    }
    latch.await();
    executor.shutdown(); // 关闭线程池
    System.out.println("所有任务完成");
}

五、实际项目案例

案例:系统启动初始化

public class SystemInitializer {
    public boolean initializeSystem() {
        System.out.println("开始系统初始化...");
        CountDownLatch latch = new CountDownLatch(4);
        AtomicBoolean success = new AtomicBoolean(true);
        // 数据库初始化
        new Thread(() -> {
            try {
                System.out.println("初始化数据库连接...");
                Thread.sleep(2000);
                System.out.println("数据库初始化完成");
            } catch (InterruptedException e) {
                success.set(false);
            } finally {
                latch.countDown();
            }
        }).start();
        // Redis初始化
        new Thread(() -> {
            try {
                System.out.println("初始化Redis连接...");
                Thread.sleep(1000);
                System.out.println("Redis初始化完成");
            } catch (InterruptedException e) {
                success.set(false);
            } finally {
                latch.countDown();
            }
        }).start();
        // 配置加载
        new Thread(() -> {
            try {
                System.out.println("加载系统配置...");
                Thread.sleep(800);
                System.out.println("配置加载完成");
            } catch (InterruptedException e) {
                success.set(false);
            } finally {
                latch.countDown();
            }
        }).start();
        // 服务注册
        new Thread(() -> {
            try {
                System.out.println("注册服务...");
                Thread.sleep(1500);
                System.out.println("服务注册完成");
            } catch (InterruptedException e) {
                success.set(false);
            } finally {
                latch.countDown();
            }
        }).start();
        try {
            boolean finished = latch.await(10, TimeUnit.SECONDS);
            if (finished && success.get()) {
                System.out.println("系统初始化成功!");
                return true;
            } else {
                System.out.println("系统初始化失败!");
                return false;
            }
        } catch (InterruptedException e) {
            System.out.println("初始化被中断");
            return false;
        }
    }
    public static void main(String[] args) {
        SystemInitializer initializer = new SystemInitializer();
        initializer.initializeSystem();
    }
}

六、总结

CountDownLatch是Java并发编程中的实用工具,它的核心价值在于:

🎯 核心特点

  • 简单易用:API简洁,概念清晰
  • 线程安全:内部实现保证多线程安全
  • 灵活应用:适合多种并发协作场景

📝 使用要点

  • 异常安全:在finally中调用countDown()
  • 超时控制:使用带超时的await()方法
  • 一次性使用:CountDownLatch不能重置
  • 合理设计:根据实际任务数量设置计数器

🚀 适用场景

  • 主线程等待多个子任务完成
  • 控制多个线程同时开始执行
  • 分段并行计算后汇总结果
  • 系统启动时的组件初始化

掌握CountDownLatch,让你的多线程程序更加优雅和高效!

到此这篇关于Java并发利器:CountDownLatch深度解析与实战应用的文章就介绍到这了,更多相关Java并发利器:CountDownLatch深度解析与实战应用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Springboot整合Swagger2后访问swagger-ui.html 404报错问题解决方案

    Springboot整合Swagger2后访问swagger-ui.html 404报错问题解决方案

    这篇文章主要介绍了Springboot整合Swagger2后访问swagger-ui.html 404报错,本文给大家分享两种解决方案,结合实例代码给大家介绍的非常详细,需要的朋友可以参考下
    2023-06-06
  • spring+netty服务器搭建的方法

    spring+netty服务器搭建的方法

    本篇文章主要介绍了spring+netty服务器搭建的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-01-01
  • springboot将mybatis升级为mybatis-plus的实现

    springboot将mybatis升级为mybatis-plus的实现

    之前项目工程用的是mybatis,现在需要将其替换为mybatis-plus,本文主要介绍了springboot将mybatis升级为mybatis-plus的实现,具有一定的参考价值,感兴趣的可以了解一下
    2023-09-09
  • Java+opencv3.2.0之直方图均衡详解

    Java+opencv3.2.0之直方图均衡详解

    这篇文章主要为大家详细介绍了Java+opencv3.2.0之直方图均衡的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-02-02
  • 手把手教你实现Java第三方应用登录

    手把手教你实现Java第三方应用登录

    本文主要介绍了手把手教你实现Java第三方应用登录,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-08-08
  • ruoyi微服务版本搭建运行方式

    ruoyi微服务版本搭建运行方式

    这篇文章主要介绍了ruoyi微服务版本搭建运行方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-12-12
  • Java中List<T> Map与Map List<T>的区别小结

    Java中List<T> Map与Map List<T>的区别小结

    本文主要介绍了Java中List<T> Map与Map List<T>的区别小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-08-08
  • 关于Java反编译字节码文件

    关于Java反编译字节码文件

    将高级语言翻译成汇编语言或机器语言的过程Java语言中的编译一般指将Java文件转换成class文件顾名思义反编译就是编译的逆向过程其实我们常用的开发工具(例如:IDEA、Eclipse)都带有反编译功能,需要的朋友可以参考下
    2023-05-05
  • 深度分析java dump文件

    深度分析java dump文件

    java内存dump是jvm运行时内存的一份快照,利用它可以分析是否存在内存浪费,可以检查内存管理是否合理,当发生OOM的时候,可以找出问题的原因。那么dump文件的内容是什么样的呢?
    2021-05-05
  • Springcould多模块搭建Eureka服务器端口过程详解

    Springcould多模块搭建Eureka服务器端口过程详解

    这篇文章主要介绍了Springcould多模块搭建Eureka服务器端口过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11

最新评论