Redisson 提供的分布式闭锁(RCountDownLatch)和栅栏(RBarrier)是两种协调多节点任务执行顺序的同步原语,它们分别基于 Redis 实现了 Java 标准库中的 CountDownLatch
和 CyclicBarrier
功能。以下从原理、用法到应用场景详细说明:
一、分布式闭锁(RCountDownLatch)
1. 核心原理
闭锁是一种一次性门闩,初始化时设置计数器值,线程通过 countDown()
方法递减计数器,当计数器变为 0 时,所有等待的线程被释放:
- 计数器初始化:如
latch.trySetCount(3)
,表示需要 3 次countDown()
操作; - 等待操作:线程调用
await()
阻塞,直到计数器为 0; - 释放操作:每次
countDown()
使计数器减 1,减至 0 时唤醒所有等待线程。
Redisson 的 RCountDownLatch 将计数器存储在 Redis 中,通过发布订阅机制实现跨节点通知。
2. 基本用法
// 创建闭锁,初始计数为3
RCountDownLatch latch = redisson.getCountDownLatch("task:latch");
latch.trySetCount(3); // 设置需要3个节点完成任务
// 工作节点代码(每个节点执行)
// ... 执行任务
latch.countDown(); // 任务完成,计数器减1
// 主线程等待所有工作节点完成
latch.await(); // 阻塞直到计数器为0
System.out.println("所有任务已完成");
3. 带超时的等待
// 等待最多10秒
boolean completed = latch.await(10, TimeUnit.SECONDS);
if (completed) {
// 所有任务完成
} else {
// 超时处理
}
二、分布式栅栏(RBarrier)
1. 核心原理
栅栏是一种循环屏障,初始化时设置参与线程数( parties),当所有线程都到达栅栏时,栅栏打开,所有线程继续执行,然后栅栏可以被重复使用:
- 初始化:如
barrier.trySetParties(5)
,表示需要 5 个线程到达; - 等待操作:线程调用
await()
阻塞,直到所有线程都到达; - 循环特性:所有线程通过后,栅栏重置,可再次使用。
Redisson 的 RBarrier 同样基于 Redis 存储状态,通过发布订阅实现跨节点同步。
2. 基本用法
// 创建栅栏,设置需要5个线程参与
RBarrier barrier = redisson.getBarrier("task:barrier");
barrier.trySetParties(5); // 设置参与线程数
// 每个工作节点执行
// ... 执行阶段1任务
barrier.await(); // 等待其他节点完成阶段1
// 所有节点都完成阶段1后,继续执行阶段2
// ... 执行阶段2任务
barrier.await(); // 等待其他节点完成阶段2
// 继续后续任务
3. 动态调整参与线程数
// 动态增加参与线程数
barrier.addParties(2); // 增加2个参与者
// 动态减少参与线程数
barrier.reduceParties(1); // 减少1个参与者
三、闭锁 vs 栅栏
特性 | 闭锁(RCountDownLatch) | 栅栏(RBarrier) |
---|---|---|
协调目标 | 等待其他线程完成(一个线程等待多个) | 所有线程互相等待(多个线程同步) |
计数器操作 | 只能 countDown() (递减) | 可动态调整 addParties /reduceParties |
循环特性 | 一次性使用,计数器到 0 后不可重置 | 可重复使用,通过后自动重置 |
典型场景 | 主线程等待多个子任务完成 | 多阶段任务的同步点(如并行计算) |
四、应用场景
1. 闭锁的典型场景
- 多节点数据同步完成后汇总:
// 主节点初始化闭锁 RCountDownLatch latch = redisson.getCountDownLatch("data:sync:latch"); latch.trySetCount(3); // 等待3个从节点同步数据 // 从节点同步数据 // ... 执行数据同步 latch.countDown(); // 同步完成,通知主节点 // 主节点等待并汇总 latch.await(); aggregateData(); // 汇总所有从节点数据
- 服务启动依赖检查:
// 初始化闭锁,等待3个关键服务启动 RCountDownLatch serviceLatch = redisson.getCountDownLatch("service:start:latch"); serviceLatch.trySetCount(3); // 每个服务启动后调用 public void startService() { // ... 启动服务 serviceLatch.countDown(); // 服务启动完成 } // 主应用等待所有服务启动 serviceLatch.await(); System.out.println("所有服务已启动,应用上线");
2. 栅栏的典型场景
- 多阶段并行计算:
// 初始化栅栏,5个计算节点参与 RBarrier barrier = redisson.getBarrier("compute:barrier"); barrier.trySetParties(5); // 每个计算节点执行 for (int phase = 1; phase <= 3; phase++) { // 执行阶段计算 computePhase(phase); // 等待所有节点完成当前阶段 barrier.await(); // 所有节点完成后,继续下一阶段 }
- 分布式游戏同步:
// 游戏初始化,10个玩家参与 RBarrier gameBarrier = redisson.getBarrier("game:barrier"); gameBarrier.trySetParties(10); // 每个玩家客户端执行 while (gameRunning) { // 处理玩家输入 processInput(); // 等待所有玩家完成当前帧 gameBarrier.await(); // 同步所有玩家状态 syncGameState(); }
五、注意事项
1. 异常处理
- 闭锁超时:使用
await(timeout, unit)
避免永久阻塞,超时后需手动处理未完成的任务。 - 栅栏破坏:若某个线程在
await()
时异常退出,可能导致栅栏无法打开,需捕获异常并重置栅栏。
2. Redis 可靠性
- 若 Redis 节点故障,可能导致同步信息丢失,需结合 Redis 的高可用部署(如哨兵、集群)。
- 长时间等待时,建议使用带超时的方法,避免 Redis 连接断开导致的永久阻塞。
3. 性能考虑
- 频繁的
countDown()
或await()
会产生 Redis 网络开销,建议批量操作或减少同步频率。 - 对于高并发场景,可考虑使用本地计数器结合定期同步到 Redis 的方式。
六、总结
- 闭锁(RCountDownLatch) 适合一次性的“等待所有任务完成”场景,如数据汇总、服务启动协调。
- 栅栏(RBarrier) 适合多阶段的任务同步,如并行计算、分布式游戏,支持循环使用。
- 两者均通过 Redis 实现跨节点同步,需注意 Redis 的可靠性和性能问题。