Java 常用并发工具类:从原理到实战
Java 并发工具类(JUC)提供了开箱即用的并发控制能力,避免我们重复造轮子。你提到的 CountDownLatch、CyclicBarrier、Semaphore、Exchanger 是最核心的四个,下面我会从核心作用、底层原理、使用场景、代码示例四个维度,把每个工具类讲清楚,方便你理解和使用。
一、CountDownLatch:倒计时门闩(一次性)
核心作用
让一个或多个线程等待其他线程完成一组操作后,再继续执行(比如主线程等待所有子线程初始化完成)。
- 核心特性:一次性,计数器减到 0 后,无法重置,只能新建对象。
底层原理
基于 AQS(AbstractQueuedSynchronizer)实现:
CountDownLatch内部维护一个计数器,初始化时设置为指定值;- 调用
countDown()时,计数器减 1(AQS 释放共享锁); - 调用
await()时,线程阻塞,直到计数器变为 0(AQS 获取共享锁成功)。
典型场景
- 主线程等待多个子线程完成初始化/任务执行;
- 并发测试:等待所有测试线程启动后,再统一开始计时。
代码示例
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
int threadNum = 3;
// 初始化计数器为3
CountDownLatch latch = new CountDownLatch(threadNum);
for (int i = 1; i <= threadNum; i++) {
int taskId = i;
new Thread(() -> {
try {
System.out.println("任务" + taskId + "开始执行");
Thread.sleep(1000); // 模拟任务执行
System.out.println("任务" + taskId + "执行完成");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 计数器减1
latch.countDown();
}
}).start();
}
// 主线程等待所有子线程完成
latch.await();
System.out.println("所有任务执行完毕,主线程继续执行");
}
}
执行结果:
任务1开始执行
任务2开始执行
任务3开始执行
任务1执行完成
任务2执行完成
任务3执行完成
所有任务执行完毕,主线程继续执行
二、CyclicBarrier:循环栅栏(可重复)
核心作用
让一组线程互相等待,直到所有线程都到达某个“栅栏点”,然后所有线程同时继续执行(比如多线程计算后,统一汇总结果)。
- 核心特性:可循环使用,计数器重置后可再次使用;支持设置「屏障动作」,所有线程到达后执行。
底层原理
基于 ReentrantLock + Condition 实现(而非直接用 AQS):
- 内部维护「等待线程数」和「栅栏数」(每次到达栅栏点的线程数);
- 线程调用
await()时,进入等待状态,直到等待线程数等于栅栏数; - 所有线程到达后,执行屏障动作(如果有),然后重置计数器,开启下一轮。
典型场景
- 多线程分阶段任务:比如“数据加载→数据计算→数据汇总”,每个阶段所有线程完成后进入下阶段;
- 模拟并发:让多个线程同时开始执行。
代码示例
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
int threadNum = 3;
// 初始化栅栏:3个线程到达后,执行屏障动作(汇总线程)
CyclicBarrier barrier = new CyclicBarrier(threadNum, () -> {
System.out.println("所有线程到达栅栏点,执行汇总操作");
});
for (int i = 1; i <= threadNum; i++) {
int taskId = i;
new Thread(() -> {
try {
System.out.println("线程" + taskId + "执行阶段1任务");
Thread.sleep(1000);
System.out.println("线程" + taskId + "到达栅栏点");
// 等待其他线程到达
barrier.await();
// 所有线程到达后,执行阶段2任务
System.out.println("线程" + taskId + "执行阶段2任务");
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
执行结果:
线程1执行阶段1任务
线程2执行阶段1任务
线程3执行阶段1任务
线程1到达栅栏点
线程2到达栅栏点
线程3到达栅栏点
所有线程到达栅栏点,执行汇总操作
线程3执行阶段2任务
线程1执行阶段2任务
线程2执行阶段2任务
三、Semaphore:信号量(限流/资源控制)
核心作用
控制同时访问某个资源的线程数(限流),通过“许可证”机制实现:
- 核心特性:支持「公平/非公平」获取许可证;可动态调整许可证数量(
release(n))。
底层原理
基于 AQS 实现:
- 初始化时设置许可证数量(AQS 的 state 变量);
- 调用
acquire()时,线程尝试获取 1 个许可证(AQS 获取共享锁),无许可证则阻塞; - 调用
release()时,线程释放 1 个许可证(AQS 释放共享锁),唤醒等待的线程。
典型场景
- 限流:比如限制同时访问数据库连接池的线程数;
- 资源池管理:比如控制同时使用的线程池/连接池数量;
- 模拟并发:控制同时执行的线程数。
代码示例
import java.util.concurrent.Semaphore;
public class SemaphoreDemo {
public static void main(String[] args) {
int permitNum = 2; // 最多2个线程同时执行
// 非公平锁(默认),如果要公平锁:new Semaphore(2, true)
Semaphore semaphore = new Semaphore(permitNum);
for (int i = 1; i <= 5; i++) {
int taskId = i;
new Thread(() -> {
try {
// 获取许可证
semaphore.acquire();
System.out.println("线程" + taskId + "获取许可证,开始执行");
Thread.sleep(1000); // 模拟任务执行
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放许可证
semaphore.release();
System.out.println("线程" + taskId + "释放许可证");
}
}).start();
}
}
}
执行结果(核心:同一时间只有2个线程执行):
线程1获取许可证,开始执行
线程2获取许可证,开始执行
线程1释放许可证
线程3获取许可证,开始执行
线程2释放许可证
线程4获取许可证,开始执行
线程3释放许可证
线程5获取许可证,开始执行
线程4释放许可证
线程5释放许可证
四、Exchanger:线程数据交换器
核心作用
让两个线程在指定点交换数据(一对一交换),如果只有一个线程到达,会阻塞直到另一个线程到达。
- 核心特性:仅支持两个线程交换;可设置超时时间(
exchange(V x, long timeout, TimeUnit unit))。
底层原理
基于 CAS + Node 节点实现(存储等待的线程和交换的数据):
- 线程 A 调用
exchange()时,创建 Node 节点并存储数据,然后自旋等待; - 线程 B 调用
exchange()时,找到线程 A 的 Node 节点,交换数据,唤醒线程 A,两者同时返回。
典型场景
- 数据校对:比如两个线程分别计算同一批数据的结果,交换后校对;
- 生产者-消费者:简单的一对一数据交换(替代队列)。
代码示例
import java.util.concurrent.Exchanger;
public class ExchangerDemo {
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
// 线程1:生产数据
new Thread(() -> {
try {
String data1 = "线程1的计算结果";
System.out.println("线程1准备交换数据:" + data1);
// 等待线程2交换数据
String data2 = exchanger.exchange(data1);
System.out.println("线程1收到交换数据:" + data2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
// 线程2:生产数据
new Thread(() -> {
try {
String data2 = "线程2的计算结果";
System.out.println("线程2准备交换数据:" + data2);
// 等待线程1交换数据
String data1 = exchanger.exchange(data2);
System.out.println("线程2收到交换数据:" + data1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
执行结果:
线程1准备交换数据:线程1的计算结果
线程2准备交换数据:线程2的计算结果
线程1收到交换数据:线程2的计算结果
线程2收到交换数据:线程1的计算结果
补充:四大工具类核心区别
| 工具类 | 核心目标 | 特性 | 底层依赖 |
|---|---|---|---|
| CountDownLatch | 一个/多线程等多线程完成 | 一次性、不可重置 | AQS(共享锁) |
| CyclicBarrier | 多线程互相等待到栅栏点 | 可循环、支持屏障动作 | ReentrantLock+Condition |
| Semaphore | 控制并发访问线程数 | 公平/非公平、可扩容 | AQS(共享锁) |
| Exchanger | 两个线程交换数据 | 一对一、可超时 | CAS+Node 节点 |
总结
- CountDownLatch:一次性等待,适合“主线程等多线程完成”的场景;
- CyclicBarrier:可循环的互相等待,适合“多线程分阶段协作”的场景;
- Semaphore:许可证限流,适合“控制资源并发访问数”的场景;
- Exchanger:一对一数据交换,适合“两个线程数据校对/简单通信”的场景。
这些工具类都是 JUC 包的核心,底层大多基于 AQS 或锁机制实现,掌握它们可以避免手写复杂的同步逻辑,提升并发代码的可读性和稳定性。
百流积聚,江河是也;文若化风,可以砾石。

浙公网安备 33010602011771号