【Java并发】Java并发编程之CountDownLatch详解:原理、使用场景与代码实战

摘要

在Java多线程编程中,CountDownLatch 是一个强大的同步工具类,用于协调多个线程的执行顺序,线程间的同步是一个常见的需求。CountDownLatch 作为 java.util.concurrent 包中的一个同步辅助类,提供了一种简单而有效的方式来实现线程间的等待和同步。本文将详细介绍 CountDownLatch 的使用方法、应用场景以及注意事项,并通过一个实战示例帮助读者更好地理解和应用这一工具。


一、CountDownLatch是什么?

定义
CountDownLatchjava.util.concurrent 包下的一个同步辅助类,允许一个或多个线程等待其他线程完成操作后再继续执行。其核心思想是通过“倒计时门闩”机制实现线程间的协作。


CountDownLatch 提供了以下两个核心方法:

  • await():使当前线程阻塞等待,直到计数器减到零。如果计数器已经为零,则立即返回。

  • countDown():将计数器减一。当计数器减到零时,所有调用 await() 的线程会被唤醒。

此外,还有一些变种方法:

  • await(long timeout, TimeUnit unit):带有超时的等待,如果超时前计数器未减到零,线程会继续执行。

  • getCount():获取当前计数器的值。


二、核心原理与特性

  1. 计数器不可重置
    CountDownLatch 的计数器初始化后不可重复使用,若需重复可用 CyclicBarrier
  2. 非独占锁
    所有线程共享计数器状态,无锁竞争问题。
  3. 典型应用场景
    • 主线程等待多个子线程初始化完成
    • 并行任务拆分与结果合并
    • 模拟高并发测试

三、代码示例与场景解析

场景1:主线程等待所有子线程完成任务

import java.util.concurrent.CountDownLatch;

public class MainThreadWaitDemo {
    public static void main(String[] args) throws InterruptedException {
        final int THREAD_COUNT = 5;
        CountDownLatch latch = new CountDownLatch(THREAD_COUNT);

        for (int i = 0; i < THREAD_COUNT; i++) {
            new Thread(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + " 开始执行任务");
                    Thread.sleep((long) (Math.random() * 1000));
                    System.out.println(Thread.currentThread().getName() + " 任务完成");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    latch.countDown(); // 确保计数器减少
                }
            }, "Worker-" + i).start();
        }

        System.out.println("主线程等待所有子线程完成任务...");
        latch.await();
        System.out.println("所有子线程任务已完成,主线程继续执行!");
    }
}

输出示例

主线程等待所有子线程完成任务...
Worker-0 开始执行任务
Worker-1 开始执行任务
Worker-2 开始执行任务
Worker-3 开始执行任务
Worker-4 开始执行任务
Worker-1 任务完成
Worker-0 任务完成
Worker-3 任务完成
Worker-2 任务完成
Worker-4 任务完成
所有子线程任务已完成,主线程继续执行!

场景2:多线程并行处理任务后合并结果

import java.util.concurrent.*;

public class ParallelTaskDemo {
    public static void main(String[] args) throws InterruptedException {
        int TASK_NUM = 3;
        ExecutorService executor = Executors.newFixedThreadPool(TASK_NUM);
        CountDownLatch latch = new CountDownLatch(TASK_NUM);
        ConcurrentHashMap<String, Integer> resultMap = new ConcurrentHashMap<>();

        // 提交并行任务
        for (int i = 0; i < TASK_NUM; i++) {
            final int taskId = i;
            executor.submit(() -> {
                try {
                    int result = processTask(taskId); // 模拟耗时计算
                    resultMap.put("Task-" + taskId, result);
                } finally {
                    latch.countDown();
                }
            });
        }

        // 等待所有任务完成
        latch.await();
        executor.shutdown();

        // 合并结果
        System.out.println("所有任务结果:" + resultMap);
    }

    private static int processTask(int taskId) throws InterruptedException {
        Thread.sleep(500); // 模拟计算耗时
        return taskId * 100;
    }
}

输出示例

所有任务结果:{Task-0=0, Task-1=100, Task-2=200}

四、CountDownLatch vs CyclicBarrier

特性 CountDownLatch CyclicBarrier
重用性 不可重用 可重用
计数器方向 递减至0 递增至指定值
主要用途 等待事件完成 线程互相等待
触发动作 支持到达屏障后执行回调动作

五、注意事项

  1. 避免死锁:确保所有线程最终都会调用 countDown()
  2. 超时控制:使用 await(long timeout, TimeUnit unit) 防止无限阻塞。
  3. 资源释放:在 finally 块中调用 countDown() 保证可靠性。

六、总结

CountDownLatch 是解决多线程协同问题的利器,尤其适用于“主从协作”或“分治合并”场景。合理使用该工具,可以显著提升程序的健壮性与可维护性。在实际项目中,需结合线程池、Future等组件灵活设计,方能发挥最大威力。

你可能感兴趣的:(Java并发,java,后端,并发编程)