Java 并发同步工具完整详解:CountDownLatch、CyclicBarrier、Semaphore、Phaser、Exchanger 使用场景与对比

0 阅读9分钟

📋 概述

一句话:多个线程怎么"等齐了再走"?JUC 同步器提供 5 种现成方案,底层全基于 AQS 框架,解决不同的线程协作问题。

为什么需要同步器?

假设你有一个批量任务:主线程启动 5 个子任务,等它们全部完成后再汇总结果。如果用 Thread.join(),代码会非常笨重。更复杂的需求——比如"5 个线程分阶段计算,每阶段全部到齐才进入下一阶段"——手写几乎不可能。

JDK 提供的同步器就是解决这类问题的轮子

同步器解决什么问题一句话
CountDownLatch等 N 个事件完成接力赛最后一棒
CyclicBarrierN 个线程互相等齐栅栏闸机
Semaphore控制并发数停车场限流
Phaser多阶段动态协调多阶段接力赛
Exchanger两线程交换数据交换礼物

💡 生活类比

5 种同步器适用场景

mermaid-diagram-2026-08-05-095051.png

类比详解

同步器生活场景核心机制
CountDownLatch接力赛最后一棒:等所有人跑完才庆祝计数减到 0,唤醒等待者
CyclicBarrier栅栏:等所有人到齐才能过闸计数加到 parties,一起通过
Semaphore停车场:只有 N 个车位,满了进不去许可数控制并发
Phaser多阶段接力赛:比 CyclicBarrier 更灵活多阶段 + 动态线程
Exchanger两人交换礼物:两两交换数据配对交换

🔍 CountDownLatch(倒计时门栓)

原理剖析

CountDownLatch 使用 AQS 的共享模式state 初始值为 count

mermaid-diagram-2026-08-05-095212.png

核心要点

  • state 初始值 = count(倒计数)
  • countDown() 每次 CAS 减 1
  • await() 阻塞,直到 state == 0 唤醒所有等待线程
  • 一次性:count=0 后不可重置

Demo 1:主线程等子任务完成

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CountDownLatchDemo1 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3);
        ExecutorService executor = Executors.newFixedThreadPool(3);
        
        for (int i = 1; i <= 3; i++) {
            final int taskId = i;
            executor.submit(() -> {
                try {
                    System.out.println("任务" + taskId + "开始执行");
                    Thread.sleep(1000);
                    System.out.println("任务" + taskId + "执行完成");
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    latch.countDown();
                }
            });
        }
        
        System.out.println("主线程等待子任务完成...");
        latch.await();
        System.out.println("所有子任务完成,主线程继续");
        executor.shutdown();
    }
}

输出

任务1开始执行
任务2开始执行
任务3开始执行
主线程等待子任务完成...
任务2执行完成
任务1执行完成
任务3执行完成
所有子任务完成,主线程继续

Demo 2:多线程汇总

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

public class CountDownLatchDemo2 {
    public static void main(String[] args) throws InterruptedException {
        int threadCount = 5;
        CountDownLatch latch = new CountDownLatch(threadCount);
        AtomicInteger total = new AtomicInteger(0);
        
        for (int i = 1; i <= threadCount; i++) {
            final int value = i;
            new Thread(() -> {
                try {
                    int result = value * 10;
                    total.addAndGet(result);
                    System.out.println("线程" + value + "计算: " + result);
                } finally {
                    latch.countDown();
                }
            }).start();
        }
        
        latch.await();
        System.out.println("汇总结果: " + total.get());
    }
}

输出

线程1计算: 10
线程3计算: 30
线程2计算: 20
线程4计算: 40
线程5计算: 50
汇总结果: 150

🔍 CyclicBarrier(循环屏障)

原理剖析

CyclicBarrier 使用 ReentrantLock + Condition 实现,parties 计数器记录等待线程数:

mermaid-diagram-2026-08-05-095233.png

核心要点

  • parties 初始值为 N,count 每次 await 减 1
  • count == 0 时,执行 barrierAction(可选),然后唤醒所有线程
  • 可循环使用:屏障触发后自动重置
  • 支持 reset() 手动重置

Demo 1:多线程分阶段计算

import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CyclicBarrierDemo1 {
    public static void main(String[] args) {
        int threadCount = 3;
        CyclicBarrier barrier = new CyclicBarrier(threadCount, () -> 
            System.out.println("--- 阶段屏障触发,进入下一阶段 ---")
        );
        
        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
        
        for (int i = 1; i <= threadCount; i++) {
            final int threadId = i;
            executor.submit(() -> {
                try {
                    for (int phase = 1; phase <= 3; phase++) {
                        System.out.println("线程" + threadId + " 阶段" + phase + "计算中...");
                        Thread.sleep(500);
                        System.out.println("线程" + threadId + " 阶段" + phase + "完成,等待其他线程");
                        barrier.await(); // 等待所有线程
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
        
        executor.shutdown();
    }
}

输出

线程1 阶段1计算中...
线程2 阶段1计算中...
线程3 阶段1计算中...
线程1 阶段1完成,等待其他线程
线程3 阶段1完成,等待其他线程
线程2 阶段1完成,等待其他线程
--- 阶段屏障触发,进入下一阶段 ---
线程2 阶段2计算中...
线程1 阶段2计算中...
线程3 阶段2计算中...
...

Demo 2:barrierAction 汇总

import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicInteger;

public class CyclicBarrierDemo2 {
    public static void main(String[] args) {
        AtomicInteger phaseResult = new AtomicInteger(0);
        CyclicBarrier barrier = new CyclicBarrier(3, () -> {
            System.out.println("当前阶段汇总: " + phaseResult.get());
            phaseResult.set(0); // 重置,为下一阶段准备
        });
        
        for (int i = 1; i <= 3; i++) {
            final int value = i;
            new Thread(() -> {
                try {
                    for (int phase = 1; phase <= 2; phase++) {
                        int result = value * phase;
                        phaseResult.addAndGet(result);
                        System.out.println("线程" + value + " 阶段" + phase + " 贡献: " + result);
                        barrier.await();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

🔍 Semaphore(信号量)

原理剖析

Semaphore 使用 AQS 的共享模式state 表示剩余许可数:

mermaid-diagram-2026-08-05-095311.png

核心要点

  • state = permits(许可数)
  • acquire() 获取许可(state-1),为 0 时阻塞
  • release() 释放许可(state+1),唤醒等待线程
  • 支持公平/非公平模式(默认非公平)

Demo 1:停车场限流

import java.util.concurrent.Semaphore;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SemaphoreDemo1 {
    public static void main(String[] args) {
        int parkingSpaces = 3;
        Semaphore semaphore = new Semaphore(parkingSpaces);
        ExecutorService executor = Executors.newFixedThreadPool(5);
        
        for (int i = 1; i <= 5; i++) {
            final int carId = i;
            executor.submit(() -> {
                try {
                    System.out.println("车辆" + carId + "正在寻找车位...");
                    semaphore.acquire();
                    System.out.println("车辆" + carId + "找到车位,开始停车");
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    System.out.println("车辆" + carId + "离开停车场");
                    semaphore.release();
                }
            });
        }
        
        executor.shutdown();
    }
}

输出

车辆1正在寻找车位...
车辆2正在寻找车位...
车辆3正在寻找车位...
车辆4正在寻找车位...
车辆5正在寻找车位...
车辆1找到车位,开始停车
车辆2找到车位,开始停车
车辆3找到车位,开始停车
车辆4找到车位,开始停车
车辆1离开停车场
车辆5找到车位,开始停车
...

Demo 2:数据库连接池限流

import java.util.concurrent.Semaphore;
import java.util.concurrent.ArrayBlockingQueue;

public class SemaphoreDemo2 {
    static class ConnectionPool {
        private final Semaphore semaphore;
        private final ArrayBlockingQueue<String> pool;
        
        public ConnectionPool(int poolSize) {
            this.semaphore = new Semaphore(poolSize);
            this.pool = new ArrayBlockingQueue<>(poolSize);
            for (int i = 0; i < poolSize; i++) {
                pool.offer("Connection-" + (i + 1));
            }
        }
        
        public String getConnection() throws InterruptedException {
            semaphore.acquire();
            String conn = pool.poll();
            System.out.println("获取连接: " + conn + ",剩余许可: " + semaphore.availablePermits());
            return conn;
        }
        
        public void releaseConnection(String conn) {
            pool.offer(conn);
            semaphore.release();
            System.out.println("释放连接: " + conn + ",剩余许可: " + semaphore.availablePermits());
        }
    }
    
    public static void main(String[] args) {
        ConnectionPool pool = new ConnectionPool(3);
        
        for (int i = 1; i <= 5; i++) {
            final int threadId = i;
            new Thread(() -> {
                try {
                    String conn = pool.getConnection();
                    Thread.sleep(1000);
                    pool.releaseConnection(conn);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }).start();
        }
    }
}

🔍 Phaser(阶段协调器)

原理剖析

Phaser 是 CyclicBarrier 的升级版,支持:

  • 动态调整参与线程数(register/deregister)
  • 多阶段协调(phase 0 → 1 → 2)
  • 分层Phaser(树形结构)

mermaid-diagram-2026-08-05-095336.png

核心 API

  • register():注册新线程
  • arrive():到达(不等待)
  • arriveAndAwaitAdvance():到达并等待
  • arriveAndDeregister():到达并注销
  • onAdvance(int phase, int registeredParties):阶段推进钩子

Demo:3阶段任务

import java.util.concurrent.Phaser;

public class PhaserDemo {
    public static void main(String[] args) {
        Phaser phaser = new Phaser(1); // 主线程注册
        
        // 启动3个worker
        for (int i = 1; i <= 3; i++) {
            phaser.register();
            final int workerId = i;
            new Thread(() -> {
                try {
                    for (int phase = 0; phase < 3; phase++) {
                        System.out.println("Worker" + workerId + " 阶段" + phase + "开始");
                        Thread.sleep(500);
                        System.out.println("Worker" + workerId + " 阶段" + phase + "完成");
                        phaser.arriveAndAwaitAdvance(); // 等待所有线程
                    }
                } finally {
                    phaser.arriveAndDeregister();
                }
            }).start();
        }
        
        // 主线程参与3个阶段
        for (int phase = 0; phase < 3; phase++) {
            phaser.arriveAndAwaitAdvance();
            System.out.println("主线程: 阶段" + phase + "全部完成");
        }
        
        phaser.arriveAndDeregister();
        System.out.println("所有阶段完成");
    }
}

🔍 Exchanger(交换器)

原理剖析

Exchanger 用于两个线程之间交换数据,基于 CAS + volatile 实现。

import java.util.concurrent.Exchanger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExchangerDemo {
    public static void main(String[] args) {
        Exchanger<String> exchanger = new Exchanger<>();
        ExecutorService executor = Executors.newFixedThreadPool(2);
        
        executor.submit(() -> {
            try {
                String data1 = "来自线程A的数据";
                System.out.println("线程A准备交换: " + data1);
                String data2 = exchanger.exchange(data1);
                System.out.println("线程A收到: " + data2);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });
        
        executor.submit(() -> {
            try {
                String data1 = "来自线程B的数据";
                System.out.println("线程B准备交换: " + data1);
                String data2 = exchanger.exchange(data1);
                System.out.println("线程B收到: " + data2);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });
        
        executor.shutdown();
    }
}

输出

线程A准备交换: 来自线程A的数据
线程B准备交换: 来自线程B的数据
线程A收到: 来自线程B的数据
线程B收到: 来自线程A的数据

📊 5种同步器对比表

特性CountDownLatchCyclicBarrierSemaphorePhaserExchanger
实现原理AQS 共享模式ReentrantLock + ConditionAQS 共享模式CAS + volatileCAS + volatile
是否可重用❌ 一次性✅ 可循环✅ 可重复使用✅ 多阶段✅ 可重复使用
计数方向减到 0加到 partiespermits 递减phase 递增N/A
等待方式N 等 1 或 1 等 NN 等 NN 等许可N 等阶段推进2 等 2
回调支持✅ barrierAction✅ onAdvance
动态线程✅ register/deregister
分层✅ 树形结构
公平性N/AN/A可选N/AN/A
性能
适用场景主从协调、压测多阶段任务、数据汇总限流、资源池复杂多阶段协调数据交换、校对

⚠️ 常见问题与踩坑

Q1: CountDownLatch 和 CyclicBarrier 区别?

维度CountDownLatchCyclicBarrier
计数方向减到 0加到 parties
等待方式N 等 1 或 1 等 NN 等 N
复用❌ 一次性✅ 可循环
回调✅ barrierAction
基于AQS 共享模式ReentrantLock + Condition
场景主线程等子任务多线程互相等齐

Q2: CountDownLatch 可以重复使用吗?

不可以。CountDownLatch 是一次性的,count=0 后无法重置。如果需要重复使用,用 CyclicBarrier 或创建新的 CountDownLatch

Q3: Semaphore 的公平性怎么选?

  • 默认非公平:性能高 3-5 倍,但可能饥饿
  • 公平模式new Semaphore(n, true),严格 FIFO,吞吐低
  • 选型:限流用非公平,避免饥饿用公平

Q4: Phaser vs CyclicBarrier?

维度CyclicBarrierPhaser
线程数固定动态(register/deregister)
阶段单阶段循环多阶段
分层✅ 树形
钩子barrierActiononAdvance(phase, parties)

结论:简单同步用 CyclicBarrier,复杂场景用 Phaser。

Q5: 什么时候用 Exchanger?

  • 生产者-消费者双缓冲
  • 遗传算法中交叉操作
  • 校对工作:两个线程各自处理一半数据,最后交换校对
  • 管线设计:前一个线程输出作为后一个输入

Q6: 同步器底层都用 AQS 吗?

同步器是否用 AQS说明
CountDownLatch✅ 共享模式state = count
CyclicBarrierReentrantLock + Condition
Semaphore✅ 共享模式state = permits
PhaserCAS + volatile
ExchangerCAS + volatile

🎯 最佳实践

选型决策表

场景推荐同步器理由
主线程等 N 个子任务完成CountDownLatch简单直接,一次性语义
多线程分阶段计算CyclicBarrier / Phaser可循环或多阶段
限流/资源池Semaphore控制并发数
复杂多阶段+动态线程Phaser灵活度最高
两线程数据交换Exchanger配对交换专用
等待服务启动CountDownLatch主从协调经典场景
并行压测CountDownLatch起始门+结束门

注意事项

  1. CountDownLatch 不可复用:需要重复等待,创建新实例或用 CyclicBarrier
  2. Semaphore release 必须 finally:避免许可泄露
  3. CyclicBarrier 异常处理:一个线程异常会导致其他线程永久等待
  4. Phaser 动态注册:确保所有线程都已注册,否则可能提前推进
  5. Exchanger 必须成对:单线程 exchange 会永久阻塞

💡 面试要点

  1. CountDownLatch 是 AQS 共享模式:state 初始值=count,countDown() CAS 减 1,await() 等 state=0 唤醒
  2. CyclicBarrier 用 ReentrantLock + Condition:不是 AQS,支持循环和 barrierAction
  3. Semaphore 限流利器:state=permits,acquire/release,支持公平/非公平
  4. Phaser 比 CyclicBarrier 更灵活:支持动态线程、多阶段、分层
  5. Exchanger 两两交换:基于 CAS,用于双缓冲、遗传算法等场景
  6. CountDownLatch vs CyclicBarrier 高频考点:一个减到 0,一个加到 parties;一个一次性,一个可循环
  7. 同步器底层都是 AQS:理解 state + CLH 队列 + CAS,同步器就是换一种 tryAcquire 实现

📝 总结

同步器核心机制适用场景
CountDownLatchAQS 共享,state 减到 0主线程等子任务
CyclicBarrierReentrantLock,N 等 N多阶段分段计算
SemaphoreAQS 共享,permits 控制限流、资源池
Phaser多阶段动态协调复杂多阶段任务
ExchangerCAS 配对交换数据交换、校对

一句话:同步器是 AQS 的应用层,掌握 AQS 原理,这 5 个工具就是"换一种状态语义"而已。