Java 锁相关详解【七、Java 并发工具类全景图:Semaphore、CountDownLatch、CyclicBarrier 与 Phaser】
Java 并发工具类全景图:Semaphore、CountDownLatch、CyclicBarrier 与 Phaser
在前几篇文章中,我们已经深入解析了 Java 锁机制以及 AQS 的底层实现原理。
AQS 并不仅仅是 ReentrantLock 的基础,它还是一整套并发工具类的核心支撑。
在 高并发编程 中,除了锁以外,我们经常需要:
- 控制线程访问资源的数量;
- 等待一组线程执行完毕;
- 让一批线程在同一时刻同步执行;
- 管理分阶段的并发任务。
JUC 提供了四个重要的工具类来解决这些问题:
- Semaphore
- CountDownLatch
- CyclicBarrier
- Phaser
本文将对它们逐一解析,并比较适用场景。
一、Semaphore:信号量
1.1 概念
Semaphore(信号量)用于控制 同时访问某个资源的线程数量。
本质上,Semaphore 维护了一个许可数 state(由 AQS 共享模式管理)。
acquire():请求一个许可,如果没有可用的许可,则阻塞。release():释放一个许可,唤醒等待线程。
1.2 示例:限流
Semaphore semaphore = new Semaphore(3); // 最多允许 3 个线程同时访问
Runnable task = () -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " 获得许可");
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
semaphore.release();
}
};
for (int i = 0; i < 10; i++) {
new Thread(task).start();
}
输出结果显示,任意时刻最多只有 3 个线程执行。
1.3 应用场景
- 数据库连接池限流
- 接口请求限流
- 控制并发访问资源
二、CountDownLatch:倒计时门闩
2.1 概念
CountDownLatch 用于 等待一组线程完成任务。
- 初始化时设置计数器值 N。
- 每次调用
countDown()计数器减 1。 - 调用
await()的线程会阻塞,直到计数器为 0。
2.2 示例:等待任务完成
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " 执行完成");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
latch.countDown();
}
}).start();
}
latch.await();
System.out.println("所有任务完成,主线程继续执行");
2.3 应用场景
- 并行任务汇总(等待所有子任务完成后再继续)。
- 服务启动依赖(主线程等待多个模块加载完成)。
三、CyclicBarrier:循环屏障
3.1 概念
CyclicBarrier 用于 让一组线程在屏障点相互等待,直到所有线程都到达后才一起继续执行。
与 CountDownLatch 的区别:
CountDownLatch是一次性的;CyclicBarrier可重用(循环屏障)。
3.2 示例:多人游戏加载
CyclicBarrier barrier = new CyclicBarrier(3,
() -> System.out.println("所有玩家已就绪,开始游戏!"));
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " 正在加载...");
Thread.sleep((long) (Math.random() * 2000));
System.out.println(Thread.currentThread().getName() + " 加载完成");
barrier.await(); // 等待其他线程
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
3.3 应用场景
- 多人协作任务(所有线程必须到齐才能执行下一步)。
- 并行计算阶段同步(如 MapReduce 的阶段切换)。
四、Phaser:分阶段同步器
4.1 概念
Phaser 是 JDK 7 引入的 分阶段同步器,功能更强大,替代部分 CyclicBarrier 和 CountDownLatch 的使用场景。
它允许:
- 动态注册/注销参与方;
- 多阶段的同步;
- 更灵活的任务协调。
4.2 示例:多阶段任务
Phaser phaser = new Phaser(3); // 初始 3 个线程
for (int i = 0; i < 3; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " 阶段1开始");
phaser.arriveAndAwaitAdvance(); // 等待其他线程
System.out.println(Thread.currentThread().getName() + " 阶段2开始");
phaser.arriveAndAwaitAdvance(); // 等待其他线程
}).start();
}
4.3 应用场景
- 分阶段计算任务(如多轮迭代的科学计算)。
- 动态线程参与(如并发下载,任务可随时加入或退出)。
五、工具类对比
| 工具类 | 核心机制 | 是否可重用 | 典型应用场景 |
|---|---|---|---|
| Semaphore | 信号量许可 | 是 | 并发访问控制(限流、资源池) |
| CountDownLatch | 倒计时 | 否 | 等待多个线程完成(一次性) |
| CyclicBarrier | 屏障同步 | 是 | 阶段性任务同步(固定线程数) |
| Phaser | 分阶段屏障 | 是 | 多阶段任务、动态参与者 |
六、工程实践建议
-
并发限流 → 用 Semaphore
- 控制访问资源的最大并发数。
-
一次性等待多个线程完成 → 用 CountDownLatch
- 典型场景:主线程等待多个子任务完成。
-
阶段性同步 → 用 CyclicBarrier
- 多个线程需要同时进入下一阶段。
-
多阶段、动态参与 → 用 Phaser
- 更复杂的分阶段任务调度。
七、总结
- Semaphore:控制并发资源访问数,适合限流场景。
- CountDownLatch:一次性等待任务完成,适合任务汇总。
- CyclicBarrier:阶段性同步点,适合需要多线程对齐的计算。
- Phaser:支持动态参与的分阶段同步器,适合复杂任务。
一句话总结:
掌握这四个工具类,可以让你在不同的并发控制场景下游刃有余,选择合适的工具胜过手写复杂的同步逻辑。
本文来自博客园,作者:NeoLshu,转载请注明原文链接:https://www.cnblogs.com/neolshu/p/19120845

浙公网安备 33010602011771号