CountDownLatch等待线程完成

wen java案例 1

本文目录导读:

CountDownLatch等待线程完成

  1. 核心概念
  2. 典型应用场景
  3. 代码示例
  4. 关键注意事项
  5. CyclicBarrier 的区别

CountDownLatch 是 Java 中一个非常实用的并发工具类,它允许一个或多个线程等待其他线程完成一组操作后再继续执行。

它的核心思想就像一个倒计数器:初始化一个计数,每当一个线程完成了它的任务,就调用 countDown() 方法让计数器减1,等待的线程会调用 await() 方法阻塞,直到计数器变为0。


核心概念

  • 初始计数:在创建 CountDownLatch 对象时设定,表示需要等待完成的事件数量。
  • countDown():事件完成时调用,计数器减1。
  • await():调用此方法的线程会被阻塞,直到计数器的值为0。
  • 一次性:计数器的值一旦降到0,就无法被重置,如果需要重置,请使用 CyclicBarrier

典型应用场景

  1. 主线程等待多个子线程完成:主线程启动N个线程执行任务,然后主线程等待所有子线程完成后再继续。
  2. 并行处理:将一个大的任务拆分成多个子任务,让多个线程并行执行,最后等待所有子任务结果汇总。
  3. 模拟高并发:用 CountDownLatch 控制多个线程同时开始执行某个操作。

代码示例

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

这是最常见的用法,主线程作为等待者,子线程作为完成者。

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        // 1. 初始化计数器为 5,表示有 5 个任务要完成
        CountDownLatch latch = new CountDownLatch(5);
        System.out.println("主线程开始,等待 " + latch.getCount() + " 个线程完成...");
        // 2. 启动 5 个子线程
        for (int i = 1; i <= 5; i++) {
            new Thread(new Worker(latch, "线程-" + i)).start();
        }
        // 3. 主线程调用 await(),在此处阻塞,直到计数器为 0
        latch.await(); 
        // 或者带超时时间的等待:latch.await(3, TimeUnit.SECONDS);
        System.out.println("主线程:所有子线程已完成,继续执行后续逻辑。");
    }
}
// 工作线程,模拟执行任务
class Worker implements Runnable {
    private final CountDownLatch latch;
    private final String name;
    public Worker(CountDownLatch latch, String name) {
        this.latch = latch;
        this.name = name;
    }
    @Override
    public void run() {
        try {
            System.out.println(name + " 开始工作...");
            Thread.sleep((long) (Math.random() * 2000)); // 模拟随机耗时工作
            System.out.println(name + " 工作完成。");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 4. 重要:任务完成后,调用 countDown() 使计数器减 1
            // 务必放在 finally 块中,避免因异常导致计数器永远无法归零
            latch.countDown();
        }
    }
}

输出示例:

主线程开始,等待 5 个线程完成...
线程-1 开始工作...
线程-2 开始工作...
线程-4 开始工作...
线程-3 开始工作...
线程-5 开始工作...
线程-5 工作完成。
线程-2 工作完成。
线程-4 工作完成。
线程-1 工作完成。
线程-3 工作完成。
主线程:所有子线程已完成,继续执行后续逻辑。

场景 2:模拟高并发(所有线程一起开始)

这里使用两个 CountDownLatch

  • startLatch (初始计数 = 1):用于让所有工作线程在同一起跑线上等待。
  • endLatch (初始计数 = 5):用于让主线程等待所有工作线程执行完毕。
import java.util.concurrent.CountDownLatch;
public class ConcurrentStartDemo {
    public static void main(String[] args) throws InterruptedException {
        int threadCount = 5;
        // 用于控制所有线程同时开始的阀门
        CountDownLatch startLatch = new CountDownLatch(1);
        // 用于主线程等待所有子线程结束
        CountDownLatch endLatch = new CountDownLatch(threadCount);
        for (int i = 0; i < threadCount; i++) {
            int finalI = i;
            new Thread(() -> {
                try {
                    // 1. 所有线程启动后,都阻塞在 startLatch.await() 这里
                    startLatch.await();
                    // 2. 当 startLatch 计数为 0 时,所有线程几乎同时开始执行
                    System.out.println("线程 " + Thread.currentThread().getName() + " 开始执行任务 " + finalI);
                    Thread.sleep((long) (Math.random() * 1000));
                    System.out.println("线程 " + Thread.currentThread().getName() + " 执行完毕");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // 3. 每个线程执行完毕后,让 endLatch 减 1
                    endLatch.countDown();
                }
            }, "Thread-" + i).start();
        }
        System.out.println("主线程准备发令...");
        Thread.sleep(2000); // 模拟准备工作
        System.out.println("主线程:预备——跑!");
        // 4. 将 startLatch 减到 0,所有等待的工作线程同时开始
        startLatch.countDown();
        // 5. 主线程等待所有工作线程结束
        endLatch.await();
        System.out.println("主线程:所有线程执行完毕,程序结束。");
    }
}

关键注意事项

  1. 计数器无法重置CountDownLatch 是一次性的,计数归零后不能再使用。
  2. countDown() 放在 finally 块中:确保无论任务是否抛出异常,计数器都能正确减1,避免死锁或程序永远等待。
  3. 避免 await() 线程无限等待:可以使用带超时参数的 await(long timeout, TimeUnit unit),防止因子线程异常或未正确调用 countDown() 导致主线程永远阻塞。
  4. getCount() 实时查询:该方法可以查询当前剩余计数,但返回的值是瞬时的,在多线程环境下不具备严格的同步语义。

CyclicBarrier 的区别

特性 CountDownLatch CyclicBarrier
可重用性 一次性,计数归零后不可用 可循环使用(通过 reset() 或自动重置)
角色 等待者(一个或多个线程)等待完成者(多个线程)完成任务 所有线程相互等待,直到所有线程都到达一个“屏障点”
核心方法 countDown()await() await()
计数器位置 在等待者(如主线程)手中 在工作线程中
典型场景 一个线程等待多个线程完成 多个线程相互等待,到达某个点后继续或执行屏障动作

一句话总结: CountDownLatch“一个人等一群人的结果”CyclicBarrier“一群人互相等,人到齐了一起走”

抱歉,评论功能暂时关闭!