🎪 并发工具类终极对比:CountDownLatch、CyclicBarrier、Semaphore、Phaser

54 阅读13分钟

一、四大金刚登场 🎬

快速识别表

工具类一句话描述生活比喻
CountDownLatch等待N个事件完成🎆 等所有烟花准备好再一起放
CyclicBarrierN个线程互相等待🏃 跑步比赛,都到终点才开始下一轮
Semaphore控制并发数量🅿️ 停车场,只有5个车位
Phaser多阶段协同🎮 游戏关卡,都过关才进下一关

二、CountDownLatch - 倒计时门栓 🔒

核心思想

就像发射火箭🚀:

检查燃料  ✅
检查引擎  ✅  
检查天气  ✅
倒计时:3... 2... 1... 🔥 发射!

所有检查完成(countDown到0),才能发射(await通过)

API

CountDownLatch latch = new CountDownLatch(3);  // 计数器=3

// 线程1、2、3
latch.countDown();  // 计数器-1

// 主线程
latch.await();  // 阻塞,直到计数器=0

完整示例

public class RocketLaunch {
    
    public static void main(String[] args) throws Exception {
        CountDownLatch latch = new CountDownLatch(3);  // 3个检查项
        
        // 检查1:燃料
        new Thread(() -> {
            System.out.println("检查燃料...");
            sleep(1000);
            System.out.println("✅ 燃料正常");
            latch.countDown();  // 完成一项
        }, "燃料检查").start();
        
        // 检查2:引擎
        new Thread(() -> {
            System.out.println("检查引擎...");
            sleep(2000);
            System.out.println("✅ 引擎正常");
            latch.countDown();  // 完成一项
        }, "引擎检查").start();
        
        // 检查3:天气
        new Thread(() -> {
            System.out.println("检查天气...");
            sleep(1500);
            System.out.println("✅ 天气良好");
            latch.countDown();  // 完成一项
        }, "天气检查").start();
        
        System.out.println("等待所有检查完成...");
        latch.await();  // 阻塞,等待计数器归零
        
        System.out.println("🚀 所有检查完成,发射!");
    }
}

输出:

检查燃料...
检查引擎...
检查天气...
等待所有检查完成...
✅ 燃料正常
✅ 天气良好
✅ 引擎正常
🚀 所有检查完成,发射!

典型应用场景

1️⃣ 并行计算,等待所有完成

public class ParallelCompute {
    public static void main(String[] args) throws Exception {
        int threadCount = 10;
        CountDownLatch latch = new CountDownLatch(threadCount);
        
        long[] results = new long[threadCount];
        
        // 10个线程并行计算
        for (int i = 0; i < threadCount; i++) {
            final int index = i;
            new Thread(() -> {
                // 计算这一段
                results[index] = compute(index);
                latch.countDown();
            }).start();
        }
        
        // 等待所有线程完成
        latch.await();
        
        // 汇总结果
        long total = Arrays.stream(results).sum();
        System.out.println("总和:" + total);
    }
}

2️⃣ 模拟并发测试

public class ConcurrentTest {
    public static void main(String[] args) throws Exception {
        int threadCount = 100;
        CountDownLatch startLatch = new CountDownLatch(1);  // 起跑枪
        CountDownLatch endLatch = new CountDownLatch(threadCount);  // 终点
        
        // 100个线程准备
        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                try {
                    startLatch.await();  // 等待起跑枪
                    
                    // 执行测试
                    testService.doSomething();
                    
                } finally {
                    endLatch.countDown();  // 到达终点
                }
            }).start();
        }
        
        System.out.println("所有线程就绪,开始!");
        long start = System.currentTimeMillis();
        
        startLatch.countDown();  // 🔫 鸣枪!所有线程同时开始
        
        endLatch.await();  // 等待所有线程结束
        
        long end = System.currentTimeMillis();
        System.out.println("测试完成,耗时:" + (end - start) + "ms");
    }
}

注意事项 ⚠️

// ❌ 错误:计数器不会重置
CountDownLatch latch = new CountDownLatch(3);
latch.countDown();
latch.countDown();
latch.countDown();  // 计数器=0
latch.await();      // 通过

// 再次使用?
latch.countDown();  // 💥 计数器已经是0,不会再减了
latch.await();      // 立即通过,不会等待

// ✅ 正确:一次性使用,不可重置
// 如果需要重复使用,用CyclicBarrier

三、CyclicBarrier - 循环栅栏 🚧

核心思想

就像团队旅游🚌:

10个人约好9点在景点门口集合
早到的人要等晚到的人
所有人都到了,才能一起进去

而且:
- 可以重复使用(去下一个景点)
- 可以设置集合后的动作(拍照📷)

API

CyclicBarrier barrier = new CyclicBarrier(
    3,           // 参与线程数
    () -> {      // 所有线程到达后的动作(可选)
        System.out.println("🎉 所有人都到了,出发!");
    }
);

// 每个线程
barrier.await();  // 阻塞,等待其他线程

完整示例

public class TourGroup {
    
    public static void main(String[] args) {
        int peopleCount = 5;
        
        CyclicBarrier barrier = new CyclicBarrier(peopleCount, () -> {
            System.out.println("✅ 所有人都到了!拍照留念 📷");
        });
        
        // 5个游客
        for (int i = 1; i <= peopleCount; i++) {
            final int id = i;
            new Thread(() -> {
                try {
                    // 第一个景点
                    System.out.println("游客" + id + "到达景点1");
                    barrier.await();  // 等待其他人
                    System.out.println("游客" + id + "开始游玩景点1");
                    
                    Thread.sleep(1000);  // 游玩中
                    
                    // 第二个景点
                    System.out.println("游客" + id + "到达景点2");
                    barrier.await();  // 又等待其他人
                    System.out.println("游客" + id + "开始游玩景点2");
                    
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, "游客" + i).start();
        }
    }
}

输出:

游客1到达景点1
游客2到达景点1
游客3到达景点1
游客4到达景点1
游客5到达景点1
✅ 所有人都到了!拍照留念 📷
游客1开始游玩景点1
游客2开始游玩景点1
游客3开始游玩景点1
游客4开始游玩景点1
游客5开始游玩景点1
游客1到达景点2
游客2到达景点2
游客3到达景点2
游客4到达景点2
游客5到达景点2
✅ 所有人都到了!拍照留念 📷
游客1开始游玩景点2
...

典型应用场景

1️⃣ 多线程计算,分阶段

public class ParallelMatrix {
    
    public static void main(String[] args) {
        int threadCount = 4;
        CyclicBarrier barrier = new CyclicBarrier(threadCount);
        
        for (int i = 0; i < threadCount; i++) {
            final int id = i;
            new Thread(() -> {
                // 阶段1:加载数据
                loadData(id);
                barrier.await();  // 等待所有线程加载完
                
                // 阶段2:计算
                compute(id);
                barrier.await();  // 等待所有线程计算完
                
                // 阶段3:写入结果
                writeResult(id);
                barrier.await();  // 等待所有线程写入完
                
            }).start();
        }
    }
}

2️⃣ 多玩家游戏

public class MultiPlayerGame {
    
    public static void main(String[] args) {
        int playerCount = 4;
        
        CyclicBarrier barrier = new CyclicBarrier(playerCount, () -> {
            System.out.println("🎮 所有玩家就绪,游戏开始!");
        });
        
        for (int i = 1; i <= playerCount; i++) {
            final int id = i;
            new Thread(() -> {
                try {
                    // 准备阶段
                    System.out.println("玩家" + id + "准备中...");
                    Thread.sleep(new Random().nextInt(3000));
                    System.out.println("玩家" + id + "准备完毕!");
                    
                    barrier.await();  // 等待所有玩家
                    
                    // 游戏开始
                    System.out.println("玩家" + id + "开始游戏");
                    
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, "玩家" + i).start();
        }
    }
}

CountDownLatch vs CyclicBarrier

对比项CountDownLatchCyclicBarrier
计数方向倒数(N→0)正数(0→N)
可重用❌ 一次性✅ 可重用
等待方一个或多个线程等待所有线程互相等待
动作触发不支持支持(barrierAction)
角色主从关系对等关系
使用场景主线程等待工作线程多线程协同工作

生活比喻对比:

CountDownLatch:
领导(主线程)等3个员工(工作线程)完成任务
员工完成后各回各家,不互相等待

CyclicBarrier:
3个朋友约好一起去玩
都到了才一起出发
可以玩多个景点(可重用)

四、Semaphore - 信号量 🚦

核心思想

停车场🅿️:

总共5个车位
现在停了3辆车,还剩2个
第6辆车来了,没车位,等待
有车离开,释放车位,第6辆可以进

信号量 = 可用资源数量

API

Semaphore semaphore = new Semaphore(5);  // 5个许可

// 获取许可
semaphore.acquire();     // 阻塞获取,直到有许可
semaphore.tryAcquire();  // 尝试获取,立即返回true/false
semaphore.tryAcquire(3, TimeUnit.SECONDS);  // 超时获取

// 释放许可
semaphore.release();

// 查询
semaphore.availablePermits();  // 可用许可数

完整示例

public class ParkingLot {
    
    private static final Semaphore PARKING = new Semaphore(5);  // 5个车位
    
    public static void main(String[] args) {
        // 10辆车
        for (int i = 1; i <= 10; i++) {
            final int carId = i;
            new Thread(() -> {
                try {
                    System.out.println("🚗 车" + carId + "到达停车场,等待车位...");
                    
                    PARKING.acquire();  // 获取车位
                    System.out.println("✅ 车" + carId + "停进车位(剩余车位:" + 
                        PARKING.availablePermits() + ")");
                    
                    // 停车
                    Thread.sleep(new Random().nextInt(5000));
                    
                    System.out.println("🚗 车" + carId + "离开车位");
                    
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    PARKING.release();  // 释放车位
                    System.out.println("✅ 车位释放(剩余车位:" + 
                        PARKING.availablePermits() + ")");
                }
            }, "车" + i).start();
        }
    }
}

输出:

🚗 车1到达停车场,等待车位...
✅ 车1停进车位(剩余车位:4)
🚗 车2到达停车场,等待车位...
✅ 车2停进车位(剩余车位:3)
🚗 车3到达停车场,等待车位...
✅ 车3停进车位(剩余车位:2)
🚗 车4到达停车场,等待车位...
✅ 车4停进车位(剩余车位:1)
🚗 车5到达停车场,等待车位...
✅ 车5停进车位(剩余车位:0)
🚗 车6到达停车场,等待车位...  ← 等待中
🚗 车7到达停车场,等待车位...  ← 等待中
...
🚗 车1离开车位
✅ 车位释放(剩余车位:1)
✅ 车6停进车位(剩余车位:0)  ← 车6进来了
...

典型应用场景

1️⃣ 限流

public class RateLimiter {
    // 限制同时只有10个请求
    private static final Semaphore LIMITER = new Semaphore(10);
    
    public void handleRequest(String request) {
        try {
            if (LIMITER.tryAcquire(100, TimeUnit.MILLISECONDS)) {
                try {
                    // 处理请求
                    processRequest(request);
                } finally {
                    LIMITER.release();
                }
            } else {
                // 限流,拒绝请求
                throw new TooManyRequestsException();
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

2️⃣ 连接池

public class ConnectionPool {
    private final Semaphore semaphore;
    private final Queue<Connection> connections;
    
    public ConnectionPool(int size) {
        this.semaphore = new Semaphore(size);
        this.connections = new ConcurrentLinkedQueue<>();
        
        // 初始化连接
        for (int i = 0; i < size; i++) {
            connections.offer(createConnection());
        }
    }
    
    public Connection getConnection() throws InterruptedException {
        semaphore.acquire();  // 获取许可
        Connection conn = connections.poll();
        return conn != null ? conn : createConnection();
    }
    
    public void releaseConnection(Connection conn) {
        connections.offer(conn);
        semaphore.release();  // 释放许可
    }
}

3️⃣ 批量任务限流

public class BatchProcessor {
    // 最多5个线程同时处理
    private static final Semaphore SEMAPHORE = new Semaphore(5);
    
    public void processBatch(List<Task> tasks) {
        tasks.forEach(task -> {
            new Thread(() -> {
                try {
                    SEMAPHORE.acquire();
                    try {
                        processTask(task);
                    } finally {
                        SEMAPHORE.release();
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }).start();
        });
    }
}

公平模式 vs 非公平模式

// 非公平模式(默认,性能高)
Semaphore unfair = new Semaphore(5, false);
// 新来的线程可能插队

// 公平模式(先来先得)
Semaphore fair = new Semaphore(5, true);
// 严格按照请求顺序分配许可

五、Phaser - 多阶段栅栏 🎮

核心思想

就像游戏闯关🎮:

第1关:打小怪
  - 所有玩家都打完 → 进入第2关

第2关:打BOSS
  - 所有玩家都打完 → 进入第3关

第3关:收集宝藏
  - 所有玩家都收集完 → 游戏结束

特点:
- 多阶段(Phase)
- 动态调整参与者数量
- 可以中途加入/退出

API

Phaser phaser = new Phaser(3);  // 3个参与者

// 线程到达并等待
phaser.arriveAndAwaitAdvance();  // 到达当前阶段,等待进入下一阶段

// 到达但不等待
phaser.arrive();

// 注册新参与者
phaser.register();

// 注销参与者
phaser.arriveAndDeregister();

// 获取当前阶段
phaser.getPhase();

完整示例

public class GameLevel {
    
    public static void main(String[] args) {
        int playerCount = 3;
        Phaser phaser = new Phaser(playerCount) {
            @Override
            protected boolean onAdvance(int phase, int registeredParties) {
                System.out.println("===== 第" + (phase + 1) + "关完成 =====\n");
                return phase >= 2;  // 3关后结束(phase从0开始)
            }
        };
        
        // 3个玩家
        for (int i = 1; i <= playerCount; i++) {
            final int id = i;
            new Thread(() -> {
                // 第1关
                System.out.println("玩家" + id + "开始第1关");
                sleep(new Random().nextInt(3000));
                System.out.println("玩家" + id + "完成第1关");
                phaser.arriveAndAwaitAdvance();
                
                // 第2关
                System.out.println("玩家" + id + "开始第2关");
                sleep(new Random().nextInt(3000));
                System.out.println("玩家" + id + "完成第2关");
                phaser.arriveAndAwaitAdvance();
                
                // 第3关
                System.out.println("玩家" + id + "开始第3关");
                sleep(new Random().nextInt(3000));
                System.out.println("玩家" + id + "完成第3关");
                phaser.arriveAndAwaitAdvance();
                
                System.out.println("🎉 玩家" + id + "通关!");
                
            }, "玩家" + i).start();
        }
    }
}

输出:

玩家1开始第1关
玩家2开始第1关
玩家3开始第1关
玩家2完成第1关
玩家1完成第1关
玩家3完成第1关
===== 第1关完成 =====

玩家1开始第2关
玩家2开始第2关
玩家3开始第2关
玩家3完成第2关
玩家1完成第2关
玩家2完成第2关
===== 第2关完成 =====

玩家1开始第3关
玩家2开始第3关
玩家3开始第3关
玩家2完成第3关
玩家3完成第3关
玩家1完成第3关
===== 第3关完成 =====

🎉 玩家1通关!
🎉 玩家2通关!
🎉 玩家3通关!

动态参与者

public class DynamicPhaser {
    
    public static void main(String[] args) throws Exception {
        Phaser phaser = new Phaser(1);  // 初始1个参与者(主线程)
        
        // 初始3个任务
        for (int i = 1; i <= 3; i++) {
            phaser.register();  // 注册参与者
            startTask(i, phaser);
        }
        
        // 等待第1阶段
        phaser.arriveAndAwaitAdvance();
        System.out.println("第1阶段完成\n");
        
        // 动态添加2个任务
        for (int i = 4; i <= 5; i++) {
            phaser.register();
            startTask(i, phaser);
        }
        
        // 等待第2阶段
        phaser.arriveAndAwaitAdvance();
        System.out.println("第2阶段完成\n");
        
        phaser.arriveAndDeregister();  // 主线程退出
    }
    
    private static void startTask(int id, Phaser phaser) {
        new Thread(() -> {
            System.out.println("任务" + id + "开始");
            sleep(1000);
            System.out.println("任务" + id + "完成");
            phaser.arriveAndDeregister();  // 完成并退出
        }).start();
    }
}

CyclicBarrier vs Phaser

对比项CyclicBarrierPhaser
阶段数单阶段(可循环)多阶段
参与者固定数量动态增减 ✨
灵活性中等高 ✨
复杂度简单复杂
性能较高较低
适用场景简单循环同步复杂多阶段协调

六、四大工具类终极对比 ⚔️

对比表

特性CountDownLatchCyclicBarrierSemaphorePhaser
作用等待事件完成线程互相等待限制并发数多阶段协调
计数器递减(N→0)递增(0→N)可增可减阶段递增
可重用
参与者固定固定动态动态 ✨
等待方主线程等工作线程线程互等不等待线程互等
典型场景并行计算多线程协同限流游戏关卡

选择决策树

需要同步吗?
  
  ├─ No  不需要这些工具
  
  └─ Yes
      
      ├─ 限制并发数?  Semaphore
      
      ├─ 多个阶段?
         ├─ Yes
            ├─ 参与者动态变化?  Phaser
            └─ 参与者固定?  CyclicBarrier
         
         └─ No(单阶段)
             ├─ 需要重复使用?  CyclicBarrier
             └─ 一次性使用?  CountDownLatch

七、实战案例 🔬

案例1:并行数据导入

// 需求:导入100万条数据,10个线程并行,主线程等待完成
public class DataImporter {
    
    public void importData(List<Data> allData) throws Exception {
        int threadCount = 10;
        CountDownLatch latch = new CountDownLatch(threadCount);
        
        int chunkSize = allData.size() / threadCount;
        
        for (int i = 0; i < threadCount; i++) {
            int start = i * chunkSize;
            int end = (i == threadCount - 1) ? allData.size() : (i + 1) * chunkSize;
            List<Data> chunk = allData.subList(start, end);
            
            new Thread(() -> {
                try {
                    // 导入这一批数据
                    batchInsert(chunk);
                } finally {
                    latch.countDown();
                }
            }).start();
        }
        
        latch.await();  // 等待所有线程完成
        System.out.println("导入完成!");
    }
}

// 为什么用CountDownLatch?
// ✅ 主线程等待工作线程
// ✅ 一次性使用
// ✅ 简单明了

案例2:多线程计算,分阶段

// 需求:矩阵运算,分3个阶段:加载→计算→保存
public class MatrixCompute {
    
    public void compute() {
        int threadCount = 4;
        CyclicBarrier barrier = new CyclicBarrier(threadCount, () -> {
            System.out.println("阶段完成");
        });
        
        for (int i = 0; i < threadCount; i++) {
            final int id = i;
            new Thread(() -> {
                // 阶段1:加载数据
                loadMatrix(id);
                barrier.await();
                
                // 阶段2:计算
                computeMatrix(id);
                barrier.await();
                
                // 阶段3:保存结果
                saveMatrix(id);
                barrier.await();
                
            }).start();
        }
    }
}

// 为什么用CyclicBarrier?
// ✅ 多个阶段
// ✅ 线程互相等待
// ✅ 可重用(3个阶段)

案例3:API限流

// 需求:限制同时最多100个请求
@Service
public class ApiService {
    
    private static final Semaphore LIMITER = new Semaphore(100);
    
    public Response handleRequest(Request request) {
        try {
            if (!LIMITER.tryAcquire(100, TimeUnit.MILLISECONDS)) {
                return Response.error("系统繁忙");
            }
            
            try {
                // 处理请求
                return processRequest(request);
            } finally {
                LIMITER.release();
            }
            
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return Response.error("请求中断");
        }
    }
}

// 为什么用Semaphore?
// ✅ 限制并发数
// ✅ 超时控制(tryAcquire)

案例4:多阶段测试

// 需求:性能测试,分5轮,每轮增加压力
public class PerformanceTest {
    
    public void test() {
        Phaser phaser = new Phaser(1) {  // 主线程
            @Override
            protected boolean onAdvance(int phase, int registeredParties) {
                System.out.println("第" + (phase + 1) + "轮完成");
                return phase >= 4;  // 5轮后结束
            }
        };
        
        for (int round = 1; round <= 5; round++) {
            final int concurrency = round * 100;  // 并发数递增
            
            // 注册参与者
            for (int i = 0; i < concurrency; i++) {
                phaser.register();
            }
            
            // 启动线程
            for (int i = 0; i < concurrency; i++) {
                new Thread(() -> {
                    sendRequest();
                    phaser.arriveAndDeregister();
                }).start();
            }
            
            phaser.arriveAndAwaitAdvance();  // 等待本轮完成
        }
        
        phaser.arriveAndDeregister();  // 主线程退出
    }
}

// 为什么用Phaser?
// ✅ 多阶段(5轮)
// ✅ 每轮参与者数量不同(动态)
// ✅ 灵活控制

八、面试应答模板 🎤

面试官:说说CountDownLatch、CyclicBarrier、Semaphore的区别?

你的回答:

这三个都是并发工具类,但作用不同:

CountDownLatch - 倒计时门栓:

  • 作用:一个或多个线程等待N个事件完成
  • 计数器:从N递减到0
  • 可重用:❌ 一次性
  • 场景:主线程等待工作线程完成,比如并行计算、批量导入
  • 例子:火箭发射,等待所有检查项完成

CyclicBarrier - 循环栅栏:

  • 作用:N个线程互相等待,都到达才继续
  • 计数器:从0递增到N
  • 可重用:✅ 可循环使用
  • 场景:多线程协同工作,分阶段执行
  • 例子:旅游团队,所有人到齐才出发

Semaphore - 信号量:

  • 作用:限制同时访问资源的线程数量
  • 计数器:可增可减
  • 可重用:✅
  • 场景:限流、连接池、停车场
  • 例子:停车场,只有5个车位

核心区别:

  1. CountDownLatch是主从关系(主线程等工作线程)
  2. CyclicBarrier是对等关系(线程互相等待)
  3. Semaphore是资源控制(限制并发数)

Phaser:

  • 是CyclicBarrier的增强版
  • 支持多阶段、动态参与者
  • 适合复杂场景,但更复杂

选择建议:

  • 简单等待 → CountDownLatch
  • 多阶段协同 → CyclicBarrier
  • 限流 → Semaphore
  • 复杂多阶段+动态参与 → Phaser

九、总结 🎯

四大并发工具类速记:

CountDownLatch 🎆
├─ 等N个事件
├─ 倒计时(N→0)
├─ 一次性
└─ 主线程等工作线程

CyclicBarrier 🚧
├─ N个线程互等
├─ 计数(0→N)
├─ 可重用
└─ 线程对等协作

Semaphore 🚦
├─ 限并发数
├─ 许可证管理
├─ acquire/release
└─ 限流、资源池

Phaser 🎮
├─ 多阶段
├─ 动态参与者
├─ 最灵活
└─ 复杂场景

记忆口诀:
门栓等完成,
栅栏互等待,
信号控数量,
阶段用Phaser!🎵

核心要点:

  • ✅ CountDownLatch:一次性,主等工
  • ✅ CyclicBarrier:可重用,线程互等
  • ✅ Semaphore:限流,许可证
  • ✅ Phaser:多阶段,动态参与

恭喜你!31-40个知识点全部完成!🎉