1. 基本用法

(1) CountDownLatch

用途:允许一个或多个线程等待其他线程完成操作。

核心方法:countDown()(减少计数器)、await()(阻塞直到计数器归零)。

不可重置,计数器归零后失效。

public static void CountDownLatchTest() throws InterruptedException {
    // 主线程等待所有子线程完成任务
    CountDownLatch latch = new CountDownLatch(3);

    for (int i = 0; i < 3; i++) {
        new Thread(() -> {
            // 子线程执行任务
            System.out.println("子线程完成" + LocalDateTime.now());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            latch.countDown(); // 计数器减1
        }).start();
    }

    latch.await(); // 主线程阻塞等待计数器归零
    System.out.println("所有子线程已完成任务后才会执行");
}

输出:

子线程完成2025-02-13T14:17:18.676
子线程完成2025-02-13T14:17:18.676
子线程完成2025-02-13T14:17:18.676
所有子线程已完成任务后才会执行

三个线程都sleep了2秒,主线程要等到所有子线程执行完毕才执行

(2) CyclicBarrier

用途:让一组线程互相等待,直到所有线程到达某个屏障点。

可重置,通过 reset() 或自动重置。

可指定一个 Runnable 任务在所有线程到达后执行。

public static void CyclicBarrierTest() {
    // 3 个线程互相等待,到达屏障后继续执行
    CyclicBarrier barrier = new CyclicBarrier(3, () -> {
        System.out.println("所有线程到达屏障");
    });

    for (int i = 0; i < 3; i++) {
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName()+"线程到达屏障" + LocalDateTime.now());
            try {
                barrier.await(); // 等待其他线程
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } catch (BrokenBarrierException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName()+"线程继续执行");
        }).start();
    }
}

输出:

Thread-2线程到达屏障2025-02-13T14:31:46.270
Thread-0线程到达屏障2025-02-13T14:31:46.270
Thread-1线程到达屏障2025-02-13T14:31:46.270
所有线程到达屏障
Thread-1线程继续执行
Thread-0线程继续执行
Thread-2线程继续执行

每个线程到达屏障之后等待,所有线程都到达屏障之后才会继续执行。

如果所有线程都到达屏障之后,Runnable 任务抛出异常会怎么样?

每个线程不会继续执行后面的代码。

如果循环次数设置从3改为6会怎么样?

CyclicBarrier 的计数器在屏障触发后会自动重置,因此可以多次使用。
6 个线程会被分成 2 组(每组 3 个线程)。
如果循环次数不是屏障数的整数倍(例如循环 5 次,屏障参数为 3),
最后一批线程会永久等待,导致程序卡死(除非处理超时或中断)。

(3) Semaphore

用途:控制同时访问某个资源的线程数量。

核心方法:acquire()(获取许可)、release()(释放许可)。

支持公平/非公平模式。

public static void SemaphoreTest() throws InterruptedException {
    // 限制同时访问资源的线程数为 2
    Semaphore semaphore = new Semaphore(2);

    for (int i = 0; i < 5; i++) {
        new Thread(() -> {
            try {
                semaphore.acquire(); // 获取许可
                System.out.println("线程占用资源" + LocalDateTime.now());
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                semaphore.release(); // 释放许可
            }
        }).start();
    }
}

输出:

线程占用资源2025-02-13T14:40:37.731
线程占用资源2025-02-13T14:40:37.731
线程占用资源2025-02-13T14:40:39.735
线程占用资源2025-02-13T14:40:39.735
线程占用资源2025-02-13T14:40:41.742

Semaphore设置为2,所以每2个线程执行完毕就不再继续执行。直到有线程 semaphore.release(); // 释放许可。观察输出的执行时间,即可得到结论。

2. 区别对比

特性CountDownLatchCyclicBarrierSemaphore
核心用途等待其他线程完成任务多个线程相互等待控制并发线程数量
计数器重置否(一次性)是(可循环使用)是(许可证可重复获取/释放)
关键方法countDown(), await()await()acquire(), release()
线程角色主线程等待子线程线程间互相等待资源访问控制
是否支持回调任务是(到达屏障后执行任务)
异常处理等待线程可能被中断屏障破坏后抛出异常无特殊处理

3. 适用场景

CountDownLatch

主线程等待所有子线程初始化完成。

并行任务完成后汇总结果。

CyclicBarrier

多线程分阶段处理数据(如 MapReduce)。

多线程协同测试(同时开始压力测试)。

Semaphore

数据库连接池限流。

控制文件读写并发数。

4. 关键总结

计数器特性:

CountDownLatch 是单向计数器(减少),不可重置。

CyclicBarrier 是循环计数器,可重复使用。

Semaphore 是许可证计数器,可动态增减。

线程协作模型:

CountDownLatch:主线程等待子线程。

CyclicBarrier:线程间互相等待。

Semaphore:限制资源访问的并发量。

高旭
40 声望3 粉丝