Java – CountDownLatch、CyclicBarrier、Semaphore 三大 JUC 同步器实战对比
目录
引言
一、JUC 同步器核心基础概念
-
2.1 同步器的本质与作用
-
2.2 核心底层支撑:AQS 同步队列
-
2.3 三大同步器的核心差异概览
-
2.1 核心设计思想与工作机制
-
2.2 底层源码实现逻辑(基于 AQS 共享模式)
-
2.3 企业级实战代码:并行任务结果汇总
-
2.4 典型业务适用场景
-
2.5 使用注意事项与避坑点
-
3.1 核心设计思想与工作机制
-
3.2 底层源码实现逻辑(ReentrantLock+Condition)
-
3.3 企业级实战代码:分片数据并行处理与合并
-
3.4 典型业务适用场景
-
3.5 使用注意事项与避坑点
-
4.1 核心设计思想与工作机制
-
4.2 底层源码实现逻辑(基于 AQS 共享模式)
-
4.3 企业级实战代码:高并发接口限流模拟
-
4.4 典型业务适用场景
-
4.5 使用注意事项与避坑点
-
5.1 场景一:多任务并行执行 & 结果汇总
-
5.2 场景二:并发流量控制与资源隔离
-
5.3 场景三:多线程分段执行 & 循环同步
-
5.4 功能特性对比表
-
6.1 测试环境与设计方案
-
6.2 吞吐量测试结果
-
6.3 延迟测试结果
-
6.4 性能差异深度分析
-
7.1 CountDownLatch 调优建议
-
7.2 CyclicBarrier 调优建议
-
7.3 Semaphore 调优建议
-
7.4 通用并发调优原则
八、选型决策指南
九、总结
引言
在 Java 多线程编程中,线程同步是保证数据安全、控制执行顺序、避免资源竞争的核心手段。java.util.concurrent(简称 JUC)包下提供了三大核心同步工具类:CountDownLatch、CyclicBarrier、Semaphore,它们基于 AQS(AbstractQueuedSynchronizer)同步框架实现,屏蔽了底层同步状态管理、线程排队、线程阻塞与唤醒的复杂逻辑,开发者可以直接使用它们实现高精度的线程协同。
但在实际项目中,很多开发者对三者的设计初衷、适用场景、底层逻辑认知模糊,经常出现滥用场景、忽略并发边界、没有处理异常边缘 Cases 等问题,导致生产环境出现线程永久阻塞、并发量过高、数据不一致等严重故障。
本文将从底层原理、实战代码、场景适配、性能对比、调优指南五个维度,对三大同步器进行全方位深度剖析。通过真实企业级业务场景代码、可量化的性能测试数据、生产级的避坑建议,帮助所有阶段的开发者(初级理解基础用法、中级掌握场景选型、高级优化底层并发逻辑)彻底掌握这三大同步器,在实际项目中做出正确的技术选型,写出稳定、高性能的并发代码。
一、JUC 同步器核心基础概念
2.1 同步器的本质与作用
同步器是一种线程协同机制,它可以控制多个线程的执行顺序、线程之间的等待 / 唤醒逻辑、以及多线程对共享资源的并发访问权限。
在并发编程中,线程的执行时序是由操作系统调度器随机决定的,无法通过常规代码控制执行先后顺序。而同步器的核心价值,就是提供了人为可控的调度能力:
-
让某个线程等待其他线程执行完成后再继续;
-
让多个线程在同一时间点统一开始执行;
-
限制同一时间内访问某个资源的最大线程数;
-
实现线程之间的结果传递与执行进度同步。
2.2 核心底层支撑:AQS 同步框架
三大同步器的底层,几乎都依赖AbstractQueuedSynchronizer(简称 AQS)同步框架。AQS 是 JUC 包的核心底层基础组件,它封装了同步状态管理、线程排队、线程阻塞与唤醒、共享 / 独占模式区分等基础同步能力,极大降低了上层同步器的实现复杂度。
AQS 的核心设计要点:
同步状态:用一个volatile int state变量表示同步状态,state 的具体含义由上层同步器自定义。
两种同步模式:
-
独占模式:同一时间只能有一个线程获取同步状态(如 ReentrantLock);
-
共享模式:同一时间可以有多个线程获取同步状态(三大同步器均基于共享模式实现)。
注:
CyclicBarrier
是唯一的例外,它没有直接使用 AQS,而是基于
ReentrantLock
(底层依赖 AQS)和
Condition
条件等待机制实现,本质上还是间接依赖了 AQS 的能力。
2.3 三大同步器的核心差异概览
| 核心逻辑 | 减法计数器,计数归零时唤醒等待线程 | 加法计数器,计数达到设定值时唤醒所有线程 | 许可证池,线程必须持有许可证才能访问资源 |
| 可重复性 | 一次性使用,计数归零后无法重置 | 可循环使用,计数归零后自动重置 | 可动态调整许可证数量,重复使用 |
| 等待主体 | 一个或多个线程,等待其他线程完成任务 | 多个线程互相等待,到达同一执行屏障 | 多个线程,等待可用的许可证资源 |
| 典型用途 | 并行任务结果汇总、主线程等待子任务执行完成 | 多线程分段执行、并行计算结果合并、模拟并发场景 | 接口限流、资源隔离、控制第三方服务并发访问量 |
| 底层同步模式 | AQS 共享模式 | ReentrantLock+Condition(间接依赖 AQS) | AQS 共享模式 |
二、深度解析 CountDownLatch:减法计数器型同步器
2.1 核心设计思想与工作机制
CountDownLatch是一种基于减法计数器的一次性同步工具。它的核心逻辑是:初始化时设定一个正整数作为计数器的初始值,一个或多个线程可以通过await()方法进入阻塞状态,等待计数器归零;其他线程在完成任务后,需要调用countDown()方法将计数器减一;当计数器的值递减到零时,所有因调用await()方法而阻塞的线程都会被自动唤醒,继续执行后续逻辑。
核心工作流程:
初始化CountDownLatch,设置计数器初始值(通常需要等待的子线程任务数量);
主线程调用await()方法,进入阻塞状态,等待计数器归零;
每个子线程任务执行完成后,调用countDown()方法,将计数器减 1;
当所有子线程都执行完成,计数器递减至 0 时,主线程被唤醒,继续执行后续汇总逻辑。
2.2 底层源码实现逻辑(基于 AQS 共享模式)
CountDownLatch的底层实现,是通过一个内部类Sync继承了 AQS 的共享模式能力。它对 AQS 的复用逻辑非常简洁:
同步状态含义:AQS 中的state变量,在这里被直接用作计数器的当前值。
获取共享锁逻辑:当线程调用await()方法时,会尝试获取共享锁,只有当state(计数器值)为 0 时,获取锁的操作才会成功;否则,线程会被封装成节点,放入 AQS 的 CLH 等待队列中阻塞。
释放共享锁逻辑:当线程调用countDown()方法时,会将state原子性减 1;如果减 1 后state的值变为 0,说明所有子任务都执行完成,此时会遍历 CLH 等待队列,唤醒所有等待的主线程。
核心源码片段(JDK8):
// CountDownLatch内部类Sync,继承AQS
private static final class Sync extends AbstractQueuedSynchronizer {
  Sync(int count) {
  setState(count); // 初始化state为计数器初始值
  }
  // 省略其他方法
}
// 构造方法,初始化计数器
public CountDownLatch(int count) {
  if (count < 0) throw new IllegalArgumentException(\”count < 0\”);
  this.sync = new Sync(count);
}
// 等待计数器归零
public void await() throws InterruptedException {
  sync.acquireShared(1); // 调用AQS的共享锁获取方法
}
// 计数器减1
public void countDown() {
  sync.releaseShared(1); // 调用AQS的共享锁释放方法
}
2.3 企业级实战代码:并行任务结果汇总
业务场景说明
在电商平台的商品详情页接口中,需要并行调用多个底层服务获取核心数据:商品基本信息、商品库存信息、商品价格信息、商品营销活动信息。这些接口之间没有依赖关系,串行调用耗时会叠加,通过CountDownLatch可以实现并行调用,主线程等待所有接口返回结果后,再组装成完整的商品详情数据返回,有效缩短接口响应时间。
完整代码示例
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
// 并行任务结果汇总示例
public class CountDownLatchBusinessDemo {
  // 模拟需要并行调用的4个业务接口
  private static final int TASK\\_COUNT = 4;
  // 创建固定线程池,管理并行任务线程
  private static final ExecutorServic
网硕互联帮助中心






评论前必须登录!
注册