Java线程协作工具:CountDownLatch 、CyclicBarrier、Phaser、Semaphore 、Exchanger

在高并发系统中,多个线程之间的协作是不可避免的。Java 从 JDK 1.5 开始引入了 java.util.concurrent(JUC)包,其中包含了一系列强大的线程协作工具类,极大简化了并发编程的复杂性。

CountDownLatch(JDK 5+):倒计时门闩

使用场景

CountDownLatch 常用于"一个或多个线程等待其他线程完成任务后再继续执行"的场景,例如:

  • 主线程等待所有子线程初始化完毕再启动服务。
  • 并发测试中模拟 N 个请求同时发出后统计总耗时。
  • 资源加载完成后统一触发后续逻辑。

核心 API

csharp 复制代码
public CountDownLatch(int count);        // 初始化计数器
public void await() throws InterruptedException;          // 阻塞等待计数归零
public boolean await(long timeout, TimeUnit unit);       // 带超时的等待
public void countDown();                                 // 计数减一

使用示例

csharp 复制代码
public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        int threadCount = 5;
        CountDownLatch latch = new CountDownLatch(threadCount);

        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + " 正在执行任务...");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    latch.countDown(); // 任务完成,计数减一
                }
            }).start();
        }

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

输出:

erlang 复制代码
Thread-0 正在执行任务...
Thread-1 正在执行任务...
Thread-3 正在执行任务...
Thread-2 正在执行任务...
Thread-4 正在执行任务...
所有任务已完成,主线程继续执行!

应用场景示例

服务启动检查:

scss 复制代码
// 等待数据库、缓存、消息队列初始化完成
CountDownLatch startupLatch = new CountDownLatch(3);
initDB(startupLatch);
initCache(startupLatch);
initMQ(startupLatch);
startupLatch.await();
startHttpServer();

使用注意点

  • 不可重用 :一旦计数归零,无法再次使用(除非新建实例)。需要重复使用的场景应考虑 CyclicBarrier
  • 若某个线程未调用 countDown(),会导致主线程永久阻塞, 导致死锁,所以务必在 finally 块中调用。
  • await() 可被中断,需处理 InterruptedException

底层原理

CountDownLatch 基于 AQS(AbstractQueuedSynchronizer) 实现:(AQS 原理详见Java并发编程:Lock原理详解

  • 内部状态 state 表示剩余计数值。
  • await() 对应 AQS 的 acquireSharedInterruptibly(1),尝试获取共享锁,若 state != 0 则入队阻塞。
  • countDown() 调用 releaseShared(1),将 state 减 1,当 state == 0 时唤醒所有等待线程。

CyclicBarrier(JDK 5+):循环屏障

使用场景

CyclicBarrier 适用于"多个线程互相等待,全部到达某一点后才能继续"的场景,例如:

  • 多线程分阶段计算(每阶段结束后汇总结果)。
  • 游戏开发中等待所有玩家准备就绪。
  • 并行算法中的同步点。

核心 API

csharp 复制代码
public CyclicBarrier(int parties); 
public CyclicBarrier(int parties, Runnable barrierAction); // 所有线程到达后执行的动作
public int await() throws InterruptedException, BrokenBarrierException;
public int await(long timeout, TimeUnit unit) throws ...;
public void reset(); // 重置屏障(可能中断当前等待)

使用示例

csharp 复制代码
public class CyclicBarrierExample {
    public static void main(String[] args) {
        CyclicBarrier barrier = new CyclicBarrier(3, () -> {
            System.out.println("所有线程已到达屏障,执行汇总操作!");
        });

        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + " 第一阶段完成");
                    barrier.await(); // 等待其他线程

                    System.out.println(Thread.currentThread().getName() + " 第二阶段开始");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

输出:

复制代码
Thread-1 第一阶段完成
Thread-2 第一阶段完成
Thread-0 第一阶段完成
所有线程已到达屏障,执行汇总操作!
Thread-0 第二阶段开始
Thread-1 第二阶段开始
Thread-2 第二阶段开始

应用场景示例

多轮并行计算:

ini 复制代码
CyclicBarrier barrier = new CyclicBarrier(4, this::aggregateResults);
for (int i = 0; i < 4; i++) {
    executor.submit(() -> {
        while (round < MAX_ROUNDS) {
            computePartialResult();
            barrier.await(); // 同步进入下一轮
            round++;
        }
    });
}

底层原理

CyclicBarrier 不基于 AQS,而是使用 ReentrantLock + Condition 实现:(Condition 原理详见Java并发编程:Lock原理详解

  • 每次调用 await(),线程进入条件队列等待。
  • 当最后一个线程到达时,唤醒所有等待线程,并执行 barrierAction(如有)。
  • 完成后自动重置计数器,支持重复使用

若某个线程在等待期间被中断或超时,屏障将进入"损坏"(broken)状态,后续调用会抛出 BrokenBarrierException

CyclicBarrier 与 CountDownLatch 的区别

特性 CountDownLatch CyclicBarrier
控制方向 一方等待多方 所有线程互相等待
是否可重用
是否支持回调 ✅(barrierAction)
底层实现 AQS ReentrantLock + Condition

Phaser(JDK 7+):灵活的阶段同步器

Phaser 新增于 JDK 7,是对 CyclicBarrierCountDownLatch 的强大扩展,支持动态注册参与者、多阶段同步和分层结构,适用于更复杂的并发协调场景。

使用场景

  • 多阶段并行任务(如 Map-Reduce 的 map → shuffle → reduce)。
  • 动态增减工作线程(例如任务中途加入新 worker)。
  • 大规模并行计算中需要分组同步(通过分层 Phaser 实现)。
  • 替代多个 CyclicBarrierCountDownLatch 的组合逻辑。

核心 API

csharp 复制代码
// 构造方法
public Phaser();                     // 无初始参与者
public Phaser(int parties);          // 指定初始参与者数量
public Phaser(Phaser parent);        // 构建父子分层结构

// 核心方法
public int register();               // 注册一个新参与者,返回当前阶段号
public int arrive();                 // 到达屏障,不等待
public int arriveAndAwaitAdvance();  // 到达并等待其他参与者
public int arriveAndDeregister();    // 到达并注销自己(退出后续阶段)
public boolean isTerminated();       // 是否已终止(当参与者数为0时终止)

使用示例

动态多阶段任务:

scss 复制代码
public class PhaserExample {
    public static void main(String[] args) {
        Phaser phaser = new Phaser(0); // 初始无参与者

        // 启动3个任务线程
        for (int i = 0; i < 3; i++) {
            phaser.register(); // 注册参与者
            new Thread(() -> {
                try {
                    for (int phase = 0; phase < 3; phase++) {
                        System.out.println(Thread.currentThread().getName() 
                            + " 完成阶段 " + phase);
                        // 到达屏障并等待其他线程
                        phaser.arriveAndAwaitAdvance();
                    }
                    // 任务结束,注销自己
                    phaser.arriveAndDeregister();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }

        // 主线程等待所有阶段完成
        while (!phaser.isTerminated()) {
            Thread.yield();
        }
        System.out.println("所有阶段已完成!");
    }
}

输出:

复制代码
Thread-1 完成阶段 0
Thread-2 完成阶段 0
Thread-0 完成阶段 0
Thread-0 完成阶段 1
Thread-2 完成阶段 1
Thread-1 完成阶段 1
Thread-2 完成阶段 2
Thread-1 完成阶段 2
Thread-0 完成阶段 2
所有阶段已完成!

高级特性:分层 Phaser

当参与者数量极大(如数千线程)时,单一 Phaser 的同步开销会很高。此时可构建树状分层结构

scss 复制代码
Phaser root = new Phaser();
Phaser group1 = new Phaser(root); // 子 Phaser,父为 root
Phaser group2 = new Phaser(root);

// group1 内部线程只与同组同步,最终由 root 汇总
group1.register();
new Thread(() -> {
    group1.arriveAndAwaitAdvance(); // 仅与 group1 同步
}).start();

这种结构能显著减少锁竞争,提升大规模并发性能。、

应用场景示例

分阶段并行处理:

scss 复制代码
// 模拟三阶段数据处理:加载 → 转换 → 输出
Phaser phaser = new Phaser(0);

List<Thread> workers = IntStream.range(0, 4)
    .mapToObj(i -> {
        phaser.register();
        return new Thread(() -> {
            load();   phaser.arriveAndAwaitAdvance();
            transform(); phaser.arriveAndAwaitAdvance();
            output(); phaser.arriveAndAwaitAdvance();
            phaser.arriveAndDeregister();
        });
    })
    .peek(Thread::start)
    .collect(Collectors.toList());

// 等待全部完成
while (!phaser.isTerminated()) {
    Thread.yield();
}

底层原理

不同于 CountDownLatchSemaphorePhaser 未基于 AQS ,而是自行实现同步机制,它的实现融合了 自旋锁 + 队列管理

  • 状态字段 :使用一个 long 类型的 state 字段,高位存阶段号(phase),低位存参与者数量(parties)。
  • 自适应等待策略
    • 参与者少时使用 自旋等待(低延迟)。
    • 参与者多或等待时间长时,自动切换到 阻塞队列(节省 CPU)。

Phaser 与 CyclicBarrier 的对比

特性 CyclicBarrier Phaser
引入版本 JDK 1.5 JDK 1.7
是否可重用 ✅(自动重置) ✅(阶段递增)
动态增减参与者 ✅(register() / arriveAndDeregister())
分层支持 ✅(父子结构)
阶段号追踪 ✅(getPhase())
终止机制 损坏即不可用 参与者归零后优雅终止
性能(大规模) 较差(全局锁) 更优(分层 + 自适应等待)

使用建议:

  • 若参与者数量固定且较少,用 CyclicBarrier 即可。
  • 若需动态调整、多阶段、大规模,则优先选择 Phaser

Semaphore(JDK 5+):信号量

使用场景

Semaphore 用于控制同时访问特定资源的线程数量,比如:

  • 数据库连接池(最多 N 个连接)。
  • 接口限流(如每秒最多处理 100 个请求)。
  • 模拟有限资源的分配(如停车场车位)。
  • new Semaphore(1) 实际上等价于 synchronizedReentrantLock

核心 API

java 复制代码
public Semaphore(int permits);
public Semaphore(int permits, boolean fair); // 公平/非公平模式
public void acquire() throws InterruptedException;
public void release();
public boolean tryAcquire(); // 非阻塞尝试获取

使用示例

scss 复制代码
public class SemaphoreExample {
    private static final Semaphore semaphore = new Semaphore(2); // 最多2个线程并发

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + " 获取许可,正在执行...");
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    semaphore.release();
                    System.out.println(Thread.currentThread().getName() + " 释放许可");
                }
            }).start();
        }
    }
}

输出:

erlang 复制代码
Thread-0 获取许可,正在执行...
Thread-1 获取许可,正在执行...
Thread-0 释放许可
Thread-2 获取许可,正在执行...
Thread-1 释放许可
Thread-3 获取许可,正在执行...
Thread-2 释放许可
Thread-3 释放许可
Thread-4 获取许可,正在执行...
Thread-4 释放许可

应用场景示例

API 限流:

java 复制代码
private final Semaphore rateLimiter = new Semaphore(100); // 每秒最多100请求

public void handleRequest() {
    if (rateLimiter.tryAcquire()) {
        try {
            process();
        } finally {
            rateLimiter.release();
        }
    } else {
        throw new RuntimeException("请求过于频繁");
    }
}

底层原理

同样基于 AQS :(AQS 原理详见Java并发编程:Lock原理详解

  • state 表示可用许可数量。
  • acquire() 尝试减少 state,若不足则入队等待。
  • release() 增加 state 并唤醒等待线程。
  • 支持公平模式(按请求顺序分配许可)。

Exchanger(JDK 5+):数据交换器

使用场景

Exchanger 专为两个线程之间交换数据而设计,典型场景如:

  • 双缓冲机制(一个线程写入缓冲区 A,另一个写入 B,满后交换)。
  • 生产者与消费者高效传递中间结果。
  • 日志收集器中交换日志缓冲区。

核心 API

java 复制代码
public V exchange(V x) throws InterruptedException;
public V exchange(V x, long timeout, TimeUnit unit) throws ...;

示例代码

ini 复制代码
public class ExchangerExample {
    public static void main(String[] args) {
        Exchanger<String> exchanger = new Exchanger<>();

        new Thread(() -> {
            try {
                String data = "Thread-A 的数据";
                System.out.println("A 准备交换: " + data);
                String received = exchanger.exchange(data);
                System.out.println("A 收到: " + received);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }).start();

        new Thread(() -> {
            try {
                Thread.sleep(1000); // 模拟延迟
                String data = "Thread-B 的数据";
                System.out.println("B 准备交换: " + data);
                String received = exchanger.exchange(data);
                System.out.println("B 收到: " + received);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }).start();
    }
}

输出:

less 复制代码
A 准备交换: Thread-A 的数据
B 准备交换: Thread-B 的数据
B 收到: Thread-A 的数据
A 收到: Thread-B 的数据

应用场景示例

双缓冲日志:

ini 复制代码
Exchanger<List<LogEntry>> exchanger = new Exchanger<>();
List<LogEntry> bufferA = new ArrayList<>(1000);
List<LogEntry> bufferB = new ArrayList<>(1000);

// 写线程不断填充 bufferA
// 写满后与读线程交换,读线程异步刷盘

底层原理

Exchanger 内部使用 Slot 或 Node 结构 暂存数据,通过 CAS + 自旋 实现高效交换:

  • 第一个调用 exchange() 的线程将数据放入槽位并等待。
  • 第二个线程到来后,取出对方数据,放入自己的数据,完成交换。
  • 若超时或中断,会清理槽位并抛出异常。

注意 :仅支持两个线程配对交换 ,第三个线程调用 exchange() 会一直阻塞直到有新的配对。

工具类对比总结

特性 CountDownLatch CyclicBarrier Phaser Semaphore Exchanger
引入版本 1.5 1.5 1.7 1.5 1.5
是否可重用
协作模式 一方等多方 所有互相等 多阶段动态同步 控制并发数 两线程交换
动态参与者
分层支持
底层机制 AQS ReentrantLock+Condition 自定义同步(CAS+自旋+队列) AQS CAS+自旋
典型用途 初始化等待 分阶段计算 复杂并行任务协调 限流 数据交换
相关推荐
Java水解2 小时前
Spring Bean生命周期深度剖析:从创建到销毁的完整旅程
spring boot·后端
Json_2 小时前
springboot框架对接物联网,配置TCP协议依赖,与设备通信,让TCP变的如此简单
java·后端·tcp/ip
+VX:Fegn08952 小时前
计算机毕业设计|基于springboot + vue职位管理推荐系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
王中阳Go2 小时前
06 Go Eino AI应用开发实战 | Eino 框架核心架构
人工智能·后端·go
Angletank2 小时前
SpringBoot用JPA接口实现分页和排序
windows·spring boot·后端
疯狂的程序猴2 小时前
生成加密 IPA 的工具在项目中的使用方式
后端
华仔啊2 小时前
Java 开发必看:什么时候用 for,什么时候用 Stream?
java·后端
程序员岳焱2 小时前
2025 IDEA运行报错:运行 xxxxApplication 时出错。命令行过长。 通过 JAR 清单或通过类路径文件缩短命令行,然后重新运行。
后端·intellij idea
Psycho_MrZhang3 小时前
Flask 设计思想总结
后端·python·flask