Java 多线程:CountDownLatch 实现线程同步教程

在本教程中,我将逐步解释如何使用 Java 中的 CountDownLatch 类实现线程同步。CountDownLatchjava.util.concurrent 包中的一个工具,它允许一个或多个线程等待其他线程完成一组操作,从而实现高效同步。教程结构清晰,包括介绍、工作原理、代码示例和解释,确保您能轻松掌握。

1. CountDownLatch 简介

CountDownLatch 是一个同步辅助类,用于协调多个线程的执行。它基于一个计数器工作:

  • 初始化时指定一个整数计数器值(例如 $n$)。
  • 线程调用 countDown() 方法减少计数器值(每次减少 1)。
  • 当计数器值达到 0 时,所有等待的线程(通过 await() 方法)被唤醒并继续执行。 这适用于场景如:主线程等待多个子线程完成初始化任务,或并行任务完成后触发后续操作。
2. 工作原理

CountDownLatch 的核心方法:

  • CountDownLatch(int count):构造函数,初始化计数器值 $count$。
  • void countDown():调用后,计数器值减少 1。如果计数器值变为 0,则唤醒所有等待线程。
  • void await():调用线程会阻塞,直到计数器值变为 0。如果计数器值已为 0,则立即返回。

工作流程:

  1. 主线程创建 CountDownLatch 实例,设置初始计数器值(例如 $n=3$)。
  2. 启动多个子线程,每个线程执行任务后调用 countDown()
  3. 主线程调用 await() 阻塞,直到所有子线程完成(计数器值变为 0)。
  4. 当计数器为 0 时,主线程恢复执行。

优点:简单高效,避免了复杂的锁机制,适用于一次性同步事件。

3. 代码示例

下面是一个完整的 Java 代码示例,模拟一个场景:主线程启动 3 个工作线程,每个线程执行一个任务(如下载文件),主线程等待所有任务完成后打印总结信息。

import java.util.concurrent.CountDownLatch;

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        // 初始化 CountDownLatch,计数器值设置为 3(表示有 3 个任务)
        final CountDownLatch latch = new CountDownLatch(3);

        // 创建并启动 3 个工作线程
        for (int i = 1; i <= 3; i++) {
            new Thread(new Worker(latch, i)).start();
        }

        System.out.println("主线程等待所有工作线程完成任务...");
        // 主线程调用 await() 阻塞,直到计数器值为 0
        latch.await();
        System.out.println("所有工作线程已完成!主线程继续执行。");
    }

    // 工作线程类
    static class Worker implements Runnable {
        private final CountDownLatch latch;
        private final int workerId;

        public Worker(CountDownLatch latch, int workerId) {
            this.latch = latch;
            this.workerId = workerId;
        }

        @Override
        public void run() {
            try {
                System.out.println("工作线程 " + workerId + " 开始执行任务...");
                // 模拟任务执行时间(随机延迟)
                Thread.sleep((long) (Math.random() * 1000));
                System.out.println("工作线程 " + workerId + " 完成任务!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // 任务完成后调用 countDown(),减少计数器值
                latch.countDown();
            }
        }
    }
}

4. 代码解释
  • 初始化CountDownLatch latch = new CountDownLatch(3); 创建一个计数器初始值为 3 的 CountDownLatch 实例。
  • 工作线程:每个 Worker 线程模拟执行任务(通过 Thread.sleep 模拟耗时)。在 run() 方法中:
    • 线程执行完任务后,在 finally 块中调用 latch.countDown(),确保计数器减少(即使发生异常)。
    • 计数器值减少表示一个任务完成。
  • 主线程
    • 调用 latch.await() 阻塞,直到计数器值变为 0。
    • 当所有工作线程完成(计数器值达到 0),主线程恢复执行并打印消息。
  • 同步效果:主线程会等待所有 3 个工作线程完成,确保“所有工作线程已完成”消息只在所有任务结束后打印。

运行此代码,您会看到输出类似:

主线程等待所有工作线程完成任务...
工作线程 1 开始执行任务...
工作线程 2 开始执行任务...
工作线程 3 开始执行任务...
工作线程 1 完成任务!
工作线程 2 完成任务!
工作线程 3 完成任务!
所有工作线程已完成!主线程继续执行。

5. 总结与注意事项
  • 优点CountDownLatch 实现简单,资源消耗低,适用于一次性同步(计数器值不能重置)。
  • 常见场景:初始化屏障(如数据库连接池初始化)、并行计算后聚合结果。
  • 注意事项
    • 计数器值初始化后不能更改。
    • 确保每个任务线程都调用 countDown(),否则 await() 可能永久阻塞。
    • 避免在任务中长时间阻塞,以免影响性能。
    • 结合其他同步工具(如 ExecutorService)使用更高效。

通过本教程,您应该能理解并应用 CountDownLatch 实现线程同步。如果有进一步问题,如异常处理或高级用法,欢迎继续咨询!

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐