- CountDownLatch(一次性屏障):一次性屏障,主线程等待N个子任务完成
- CyclicBarrier(可重用屏障):可重复使用,所有线程到达屏障后继续执行
- Semaphore(信号量模型):用于控制并发线程数,如数据库连接池限制
CountDownLatch
工作原理:
- 初始化时设定计数器
- 每个工作线程完成任务后调用countDown()减少计数
- 主线程通过await()阻塞等待,直到计数器归零
适用场景:
- 服务启动依赖(如需要先启动数据库再启动主应用)
- 并行计算汇总(多个计算任务完成后合并结果)
- 压力测试(等待所有测试线程就绪后同时发起请求)
注意事项:
- 计数器不可重置,如果需要重复使用应选择CyclicBarrier
- await()可以设置超时时间避免无限等待
- 建议将countDown()放在finally块确保执行
- 避免在异步回调方法之外操作计数器
与Thread.join()的区别:
- 更灵活:不需要直接控制线程实例
- 更精细:可以控制多个不同类型的任务
- 更智能:支持超时机制和状态查询
性能特点:
- 基于AQS实现,await()会触发线程排队
- 适用于低频次同步场景(初始化、阶段检查等)
- 不适合高频同步操作(考虑用Phaser替代)
以服务启动时,需要首先预热依赖的其他服务为例
/**
* @author 反卷但卷
* @version 1.0
* @description CountDownLatch的应用 Demo
*/
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadLocalRandom;
public class ServerClusterBootstrapper {
// 模拟服务器节点启动流程
public static void main(String[] args) throws InterruptedException {
// 需要等待3个核心服务启动
final CountDownLatch serviceLatch = new CountDownLatch(3);
new Thread(() -> startDatabase(serviceLatch), "DB-Thread").start();
new Thread(() -> startCache(serviceLatch), "Cache-Thread").start();
new Thread(() -> startMQ(serviceLatch), "MQ-Thread").start();
System.out.println("主线程开始等待基础服务初始化...");
long startTime = System.currentTimeMillis();
serviceLatch.await();
System.out.printf("所有基础服务就绪,总耗时%dms\n启动主应用...\n",
System.currentTimeMillis() - startTime);
}
// 模拟数据库服务启动
private static void startDatabase(CountDownLatch latch) {
try {
int delay = ThreadLocalRandom.current().nextInt(800, 1200);
Thread.sleep(delay);
System.out.println(Thread.currentThread().getName()
+ " 数据库连接建立完成 (" + delay + "ms)");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
latch.countDown();
}
}
// 模拟缓存服务启动
private static void startCache(CountDownLatch latch) {
try {
int delay = ThreadLocalRandom.current().nextInt(500, 1000);
Thread.sleep(delay);
System.out.println(Thread.currentThread().getName()
+ " 缓存预热完成 (" + delay + "ms)");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
latch.countDown();
}
}
// 模拟消息队列服务启动
private static void startMQ(CountDownLatch latch) {
try {
int delay = ThreadLocalRandom.current().nextInt(300, 1500);
Thread.sleep(delay);
System.out.println(Thread.currentThread().getName()
+ " 消息队列通道创建成功 (" + delay + "ms)");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
latch.countDown();
}
}
}
CyclicBarrier
运作原理:
- 初始化时指定参与线程数和屏障动作(Runnable)
- 每个线程调用await()后进入等待
- 当全部线程到达屏障点时,自动执行屏障动作
- 计数器自动重置,可重复使用
典型使用场景:
- 多阶段计算(如ETL流程的分阶段执行)
- 迭代运算(每轮迭代需要同步状态)
- 离散事件模拟(如游戏帧同步)
- 压力测试中的波浪式请求
关键注意事项:
- await()方法返回每个线程的到达索引(可用于领导选举)
- 重置机制:reset()方法会强制进入broken状态
- 如果一个线程在等待中失败,其他线程会收到BrokenBarrierException
- 屏障动作在最后一个线程到达后由任意线程执行
与CountDownLatch的对比:
- 重用性:CyclicBarrier可重复触发,CountDownLatch一次性
- 等待方向:CyclicBarrier是多线程互相等待,CountDownLatch是主线程等待工作线程
- 扩展功能:CyclicBarrier支持屏障动作和到达状态查询
以大批量数据分片并行处理为例
/**
* @author 反卷但卷
* @version 1.0
* @description CyclicBarrier的使用 Demo
*/
import java.util.concurrent.*;
public class DataProcessingPipeline {
// 模拟3个数据分片并行处理
private static final int WORKER_COUNT = 3;
public static void main(String[] args) {
// 设置3个阶段的屏障,每个阶段结束后执行统计操作
CyclicBarrier barrier = new CyclicBarrier(WORKER_COUNT, () -> {
System.out.printf("阶段完成,当前时间:%s%n", System.currentTimeMillis());
});
ExecutorService executor = Executors.newFixedThreadPool(WORKER_COUNT);
for (int i = 1; i <= WORKER_COUNT; i++) {
final int partitionId = i;
executor.submit(() -> processDataPartition(partitionId, barrier));
}
executor.shutdown();
}
private static void processDataPartition(int partitionId, CyclicBarrier barrier) {
try {
// 第一阶段:数据加载
stageWork("加载", partitionId, 200);
barrier.await();
// 第二阶段:数据清洗
stageWork("清洗", partitionId, 500);
barrier.await();
// 第三阶段:数据持久化
stageWork("保存", partitionId, 300);
barrier.await();
System.out.printf("分片%d 全部流程完成%n", partitionId);
} catch (InterruptedException | BrokenBarrierException e) {
Thread.currentThread().interrupt();
System.err.println("处理过程被中断");
}
}
private static void stageWork(String stageName, int partitionId, int baseTime)
throws InterruptedException {
int delay = ThreadLocalRandom.current().nextInt(baseTime, baseTime + 200);
Thread.sleep(delay);
System.out.printf("分片%d %s完成 (耗时%dms)%n",
partitionId, stageName, delay);
}
}
Semaphore
工作流程:
- 初始化时指定资源总量(许可证数量)
- 请求资源:acquire() 或 tryAcquire()
- 使用资源:执行临界区代码
- 释放资源:release()
适用场景:
- 连接池/对象池资源管理
- 接口限流(特别是突发流量控制)
- 生产者消费者模式(有界缓冲区)
- 并行计算任务调度
关键方法对比:
| 方法 | 是否阻塞 | 支持超时 | 立即返回 |
|---|---|---|---|
| acquire() | √ | × | × |
| acquireUninterruptibly() | √ | × | × |
| tryAcquire() | × | × | √ |
| tryAcquire(timeout) | △ | √ | × |
注意事项:
- 释放许可数量可以超过初始值(允许动态调整池大小)
- 建议在finally块中释放许可
- 使用公平模式会降低吞吐量但保证公平性
- 单个线程可以多次获取许可(需相应次数释放)
- 监控指标:availablePermits()、getQueueLength()
以控制连接池连接数量为例
/**
* @author 反卷但卷
* @version 1.0
* @description Semaphore的使用 Demo,信号量
*/
import java.util.concurrent.*;
public class DatabaseConnectionPool {
private static final int POOL_SIZE = 5;
private static final Semaphore availableConnections = new Semaphore(POOL_SIZE, true);
private static final int TOTAL_THREADS = 10;
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(TOTAL_THREADS);
// 模拟突发流量:10个并发请求
for (int i = 1; i <= TOTAL_THREADS; i++) {
final int taskId = i;
executor.execute(() -> {
try {
queryDatabase(taskId);
} catch (InterruptedException e) {
System.err.println("任务" + taskId + "被中断");
Thread.currentThread().interrupt();
}
});
}
executor.shutdown();
}
private static void queryDatabase(int taskId) throws InterruptedException {
System.out.printf("任务%d 等待连接... (可用许可:%d)%n",
taskId, availableConnections.availablePermits());
// 设置等待超时时间
if (!availableConnections.tryAcquire(3, TimeUnit.SECONDS)) {
System.err.println("任务" + taskId + " 等待超时,拒绝服务");
return;
}
try (ConnectionProxy connection = new ConnectionProxy(taskId)) {
System.out.printf("任务%d 获取连接 (剩余许可:%d)%n",
taskId, availableConnections.availablePermits());
// 模拟SQL执行时间
Thread.sleep(ThreadLocalRandom.current().nextInt(500, 1500));
System.out.println("任务" + taskId + " 查询完成");
} catch (Exception e) {
System.err.println("任务" + taskId + " 执行异常:" + e.getMessage());
} finally {
availableConnections.release();
System.out.printf("任务%d 释放连接 (当前可用:%d)%n",
taskId, availableConnections.availablePermits());
}
}
// 模拟数据库连接资源
private static class ConnectionProxy implements AutoCloseable {
private final int taskId;
public ConnectionProxy(int taskId) {
this.taskId = taskId;
}
@Override
public void close() {
// 实际项目应包含连接回收逻辑
}
}
}