Java并发工具类详解:Semaphore、CyclicBarrier与CountDownLatch
在多线程编程中,线程间的同步与协作是核心难题。Java并发包(java.util.concurrent
)提供了多种工具类,简化了复杂场景下的线程协调逻辑。本文将深入解析三个常用工具类------Semaphore
、CyclicBarrier
和CountDownLatch
,通过场景案例、代码实现和对比分析,理清它们的适用场景与核心区别。
一、Semaphore:控制并发访问的"信号灯"
1. 核心功能
Semaphore
(信号量)用于限制同时访问某个资源的线程数量,类似"限流"机制。它维护一组"许可"(permits),线程需先获取许可才能访问资源,访问完成后释放许可供其他线程使用。
2. 适用场景
- 控制并发请求数(如接口限流)
- 管理资源池访问(如数据库连接池、线程池)
- 实现生产者-消费者模型中的缓冲区边界控制
3. 代码案例:数据库连接池限流
假设数据库最多支持3个并发连接,超过则需等待:
java
import java.util.concurrent.Semaphore;
public class SemaphoreDemo {
public static void main(String[] args) {
// 初始化3个许可(最多3个线程同时访问)
Semaphore semaphore = new Semaphore(3);
System.out.println("=== 数据库连接池初始化完成,最大并发连接数:3 ===");
// 模拟10个线程竞争访问数据库
for (int i = 0; i < 10; i++) {
final int threadId = i + 1;
new Thread(() -> {
try {
System.out.println("线程" + threadId + "尝试获取数据库连接...");
// 获取许可(若满则阻塞等待)
semaphore.acquire();
System.out.println("线程" + threadId + "✅ 获取到数据库连接,开始执行SQL操作...");
Thread.sleep(1000); // 模拟数据库操作耗时
System.out.println("线程" + threadId + "📝 SQL操作执行完成");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放许可(必须在finally中执行,避免资源泄漏)
semaphore.release();
System.out.println("线程" + threadId + "🔄 释放数据库连接,当前可用连接数:" + semaphore.availablePermits());
}
}).start();
// 错开线程启动时间,便于观察输出
Thread.sleep(200);
}
}
}
输出示例:
=== 数据库连接池初始化完成,最大并发连接数:3 ===
线程1尝试获取数据库连接...
线程1✅ 获取到数据库连接,开始执行SQL操作...
线程2尝试获取数据库连接...
线程2✅ 获取到数据库连接,开始执行SQL操作...
线程3尝试获取数据库连接...
线程3✅ 获取到数据库连接,开始执行SQL操作...
线程4尝试获取数据库连接...
线程1📝 SQL操作执行完成
线程1🔄 释放数据库连接,当前可用连接数:1
线程4✅ 获取到数据库连接,开始执行SQL操作...
线程5尝试获取数据库连接...
线程2📝 SQL操作执行完成
线程2🔄 释放数据库连接,当前可用连接数:1
线程5✅ 获取到数据库连接,开始执行SQL操作...
...(后续线程按同样逻辑执行)
二、CyclicBarrier:多线程协同的"循环屏障"
1. 核心功能
CyclicBarrier
(循环屏障)让多个线程相互等待 ,直到所有线程都到达某个"屏障点"后,再一起继续执行。与其他工具类不同,它支持重复使用(通过reset()
重置屏障)。
2. 适用场景
- 多阶段任务(如"需求分析→编码→测试"的开发流程)
- 数据分片处理(各线程处理完分片后,等待汇总结果)
- 模拟并发场景(所有线程就绪后同时执行)
3. 代码案例:多阶段开发流程
3个开发者需依次完成三个阶段任务,每个阶段必须全员完成后才能进入下一阶段:
java
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
int developerCount = 3; // 3个开发者线程
System.out.println("=== 项目启动,共" + developerCount + "名开发者参与 ===");
// 屏障触发时执行的任务(可选,如阶段总结)
CyclicBarrier barrier = new CyclicBarrier(developerCount, () -> {
System.out.println("======================================");
System.out.println("📢 所有开发者已完成当前阶段,准备进入下一阶段!");
System.out.println("======================================");
});
for (int i = 0; i < developerCount; i++) {
final int developerId = i + 1;
new Thread(() -> {
try {
// 第一阶段:需求分析
System.out.println("开发者" + developerId + ":开始需求分析(阶段1)");
Thread.sleep((long) (Math.random() * 1000) + 500); // 模拟不同耗时
System.out.println("开发者" + developerId + ":需求分析完成(阶段1),等待其他人...");
barrier.await(); // 等待其他开发者
// 第二阶段:编码
System.out.println("开发者" + developerId + ":开始编码开发(阶段2)");
Thread.sleep((long) (Math.random() * 1000) + 500);
System.out.println("开发者" + developerId + ":编码开发完成(阶段2),等待其他人...");
barrier.await();
// 第三阶段:测试
System.out.println("开发者" + developerId + ":开始功能测试(阶段3)");
Thread.sleep((long) (Math.random() * 1000) + 500);
System.out.println("开发者" + developerId + ":功能测试完成(阶段3),等待其他人...");
barrier.await();
System.out.println("开发者" + developerId + ":🎉 所有阶段任务完成!");
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
输出示例:
=== 项目启动,共3名开发者参与 ===
开发者1:开始需求分析(阶段1)
开发者2:开始需求分析(阶段1)
开发者3:开始需求分析(阶段1)
开发者2:需求分析完成(阶段1),等待其他人...
开发者1:需求分析完成(阶段1),等待其他人...
开发者3:需求分析完成(阶段1),等待其他人...
======================================
📢 所有开发者已完成当前阶段,准备进入下一阶段!
======================================
开发者1:开始编码开发(阶段2)
开发者2:开始编码开发(阶段2)
开发者3:开始编码开发(阶段2)
开发者3:编码开发完成(阶段2),等待其他人...
开发者1:编码开发完成(阶段2),等待其他人...
开发者2:编码开发完成(阶段2),等待其他人...
======================================
📢 所有开发者已完成当前阶段,准备进入下一阶段!
======================================
...(第三阶段流程类似)
三、CountDownLatch:等待前置任务的"倒计时器"
1. 核心功能
CountDownLatch
(倒计时器)让一个或多个线程等待其他线程完成指定操作 后再执行。它维护一个计数器,线程完成任务后调用countDown()
递减计数器,当计数器为0时,所有等待的线程被唤醒。计数器不可重置,为一次性工具。
2. 适用场景
- 主线程等待子线程初始化完成(如服务器启动前的准备工作)
- 汇总多线程任务结果(如多个线程计算部分结果,主线程汇总)
- 模拟并发测试(等待所有线程就绪后同时执行)
3. 代码案例:服务器启动前的初始化
服务器启动前,需等待5个初始化任务(如加载配置、连接数据库)完成:
java
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
int taskCount = 5; // 5个初始化任务
CountDownLatch latch = new CountDownLatch(taskCount);
System.out.println("=== 服务器启动流程开始,需完成" + taskCount + "项初始化任务 ===");
// 启动初始化任务
for (int i = 0; i < taskCount; i++) {
final int taskId = i + 1;
new Thread(() -> {
try {
System.out.println("初始化任务" + taskId + ":启动(当前剩余任务数:" + latch.getCount() + ")");
// 模拟不同任务耗时(1-2秒)
Thread.sleep((long) (Math.random() * 1000) + 1000);
System.out.println("初始化任务" + taskId + ":执行成功");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown(); // 任务完成,计数器-1
System.out.println("初始化任务" + taskId + ":已标记完成(剩余任务数:" + latch.getCount() + ")");
}
}).start();
}
// 主线程等待所有初始化完成
System.out.println("=== 主线程等待所有初始化任务完成... ===");
latch.await(); // 阻塞直到计数器为0
System.out.println("=== 所有初始化任务完成,服务器启动成功!🎉 ===");
}
}
输出示例:
=== 服务器启动流程开始,需完成5项初始化任务 ===
初始化任务1:启动(当前剩余任务数:5)
初始化任务2:启动(当前剩余任务数:5)
初始化任务3:启动(当前剩余任务数:5)
初始化任务4:启动(当前剩余任务数:5)
初始化任务5:启动(当前剩余任务数:5)
=== 主线程等待所有初始化任务完成... ===
初始化任务3:执行成功
初始化任务3:已标记完成(剩余任务数:4)
初始化任务1:执行成功
初始化任务1:已标记完成(剩余任务数:3)
初始化任务5:执行成功
初始化任务5:已标记完成(剩余任务数:2)
初始化任务2:执行成功
初始化任务2:已标记完成(剩余任务数:1)
初始化任务4:执行成功
初始化任务4:已标记完成(剩余任务数:0)
=== 所有初始化任务完成,服务器启动成功!🎉 ===
四、三者核心区别对比
特性 | Semaphore | CyclicBarrier | CountDownLatch |
---|---|---|---|
核心功能 | 控制并发访问的线程数量(限流) | 多线程相互等待,一起继续执行 | 等待其他线程完成后再执行 |
计数器/许可 | 许可数量(可动态调整) | 参与线程数(可通过reset() 重置) |
等待的任务数(一次性递减) |
阻塞对象 | 申请许可的线程(争夺资源的线程) | 所有参与的线程(相互等待) | 调用await() 的线程(等待者) |
复用性 | 可重复使用(许可释放后可再次获取) | 可重复使用(reset() 重置屏障) |
一次性(计数器到0后无法重置) |
典型场景 | 限流(连接池、并发请求控制) | 多阶段协同任务(分步骤流程) | 等待前置任务完成(初始化、汇总) |
核心方法 | acquire() /release() |
await() /reset() |
countDown() /await() |
五、总结
- Semaphore 是"门卫",通过许可机制控制同时访问资源的线程数,适合限流场景(如连接池、接口并发控制)。
- CyclicBarrier 是"集合点",让多个线程到达屏障后一起执行下一阶段,适合多步骤协同任务(如分阶段开发流程)。
- CountDownLatch 是"发令枪",等待所有前置任务完成后触发后续操作,适合初始化、结果汇总等场景。
这三个工具类均基于AQS(AbstractQueuedSynchronizer)实现,但设计目标不同。实际开发中需根据具体同步需求选择:如需控制并发量用Semaphore
,如需多线程协同用CyclicBarrier
,如需等待前置任务用CountDownLatch
。