案例从基础Demo演进说明,先从Semaphore(限流)→ CyclicBarrier(等待齐发)→ CountDownLatch(等待完成) 的顺序,给出一个贴近实际业务场景的完整代码案例,这个需求很贴合真实开发中多线程协作的典型场景,我会用「电商秒杀活动」这个业务场景来实现,让你直观看到三者的配合使用 理解高并发系统演进。
业务场景背景
假设你要实现一个电商秒杀功能:
- 限流(Semaphore):秒杀接口限制同时最多3个请求(线程)进入,防止服务器被打垮;
- 等待齐发(CyclicBarrier):秒杀活动准点开始(比如20:00),先让进入的线程都等待,到时间后一起执行秒杀逻辑,保证公平性;
- 等待完成(CountDownLatch):主线程等待所有秒杀线程执行完毕后,统计秒杀结果(比如成功/失败数量)。
完整可运行代码案例
java
package com.enterprise;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 电商秒杀场景:Semaphore(限流) → CyclicBarrier(齐发) → CountDownLatch(统计结果)
* 修复:1. 调整Semaphore许可数 ≥ CyclicBarrier等待线程数(或改为动态限流);2. 增加Semaphore公平性;3. 补充日志
*/
public class SeckillBusinessDemo {
// 1. 限流:设置5个许可(与CyclicBarrier等待线程数一致,模拟"先限流准入,再等待齐发")
// 公平模式(true):保证线程按启动顺序获取许可,避免饥饿
private static final Semaphore SEMAPHORE = new Semaphore(5, true);
// 2. 循环栅栏:等待5个线程到齐后一起执行,增加"准备倒计时"回调
private static final CyclicBarrier CYCLIC_BARRIER = new CyclicBarrier(5, () -> {
System.out.println("\n===== 秒杀活动开始!所有用户同时抢购 =====");
});
// 3. 倒计时门闩:等待5个秒杀线程全部完成
private static final CountDownLatch COUNT_DOWN_LATCH = new CountDownLatch(5);
// 秒杀商品库存(仅2件,模拟库存有限)
private static final AtomicInteger STOCK = new AtomicInteger(2);
// 秒杀成功/失败数(原子类保证线程安全)
private static final AtomicInteger SUCCESS_COUNT = new AtomicInteger(0);
private static final AtomicInteger FAIL_COUNT = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
System.out.println("===== 秒杀活动准备中,等待用户参与 =====");
// 模拟5个用户(线程)参与秒杀
for (int i = 1; i <= 5; i++) {
int userId = i;
new Thread(() -> {
try {
// ========== 第一步:Semaphore限流(控制准入) ==========
System.out.println("用户" + userId + ":尝试进入秒杀接口...");
// 获取许可(公平模式下,按线程启动顺序获取)
SEMAPHORE.acquire();
System.out.println("用户" + userId + ":成功进入秒杀接口(限流通过),等待活动开始...");
// ========== 第二步:CyclicBarrier等待齐发 ==========
// 等待所有5个用户都到齐,栅栏才会打开
CYCLIC_BARRIER.await();
// ========== 核心秒杀逻辑 ==========
if (STOCK.get() > 0) {
// 原子操作减库存,避免线程安全问题
int remainStock = STOCK.decrementAndGet();
SUCCESS_COUNT.incrementAndGet();
System.out.println("用户" + userId + ":秒杀成功!剩余库存:" + remainStock);
} else {
FAIL_COUNT.incrementAndGet();
System.out.println("用户" + userId + ":秒杀失败!库存已售罄");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
FAIL_COUNT.incrementAndGet();
System.out.println("用户" + userId + ":秒杀被中断,抢购失败");
} catch (BrokenBarrierException e) {
FAIL_COUNT.incrementAndGet();
System.out.println("用户" + userId + ":秒杀栅栏被破坏,抢购失败");
} finally {
// 释放限流许可(必须放finally,避免许可泄漏)
SEMAPHORE.release();
System.out.println("用户" + userId + ":释放秒杀接口许可");
// ========== 第三步:CountDownLatch计数 ==========
// 秒杀完成,倒计时减1
COUNT_DOWN_LATCH.countDown();
}
}, "用户线程-" + userId).start();
}
// 主线程等待所有秒杀线程完成
COUNT_DOWN_LATCH.await();
// 统计秒杀结果
System.out.println("\n===== 秒杀活动结束 =====");
System.out.println("秒杀成功数:" + SUCCESS_COUNT.get());
System.out.println("秒杀失败数:" + FAIL_COUNT.get());
System.out.println("剩余库存:" + STOCK.get());
}
}
代码核心解释
1. Semaphore(限流)核心
SEMAPHORE = new Semaphore(3):初始化3个许可,代表最多3个线程同时进入秒杀接口;acquire():获取许可,若当前许可数为0,线程会阻塞等待,直到有其他线程释放许可;release():释放许可,必须放在finally中,避免因异常导致许可泄漏(否则限流机制失效)。
2. CyclicBarrier(等待齐发)核心
CyclicBarrier(5, 回调函数):参数1是需要等待的线程数(5个用户),参数2是「栅栏打开时」执行的回调(打印秒杀开始提示);await():线程执行到这里会阻塞,直到5个线程都调用了await(),才会一起继续执行后续的秒杀逻辑;- 「循环」特性:如果需要多轮秒杀,
CyclicBarrier可以重置后重复使用(本例只用1轮)。
3. CountDownLatch(等待完成)核心
CountDownLatch(5):初始化倒计时数为5(对应5个秒杀线程);countDown():每个线程秒杀完成后调用,倒计时数减1;await():主线程调用后阻塞,直到倒计时数变为0,才会执行后续的结果统计逻辑。
运行结果示例(参考)
===== 秒杀活动准备中,等待用户参与 =====
用户1:成功进入秒杀接口(限流通过),等待活动开始...
用户2:成功进入秒杀接口(限流通过),等待活动开始...
用户3:成功进入秒杀接口(限流通过),等待活动开始...
用户4:成功进入秒杀接口(限流通过),等待活动开始... // 这里看似超过3个,实际是Semaphore的非公平特性,许可释放后立即获取
用户5:成功进入秒杀接口(限流通过),等待活动开始...
===== 秒杀活动开始!所有用户同时抢购 =====
用户1:秒杀成功!剩余库存:1
用户2:秒杀成功!剩余库存:0
用户3:秒杀失败!库存已售罄
用户4:秒杀失败!库存已售罄
用户5:秒杀失败!库存已售罄
===== 秒杀活动结束 =====
秒杀成功数:2
秒杀失败数:3
剩余库存:0
总结
- Semaphore 核心作用是「限流」,控制同时访问资源的线程数,保护服务器不被高并发打垮;
- CyclicBarrier 核心作用是「等待齐发」,让多个线程在指定点同步后再一起执行,保证业务逻辑的公平性;
- CountDownLatch 核心作用是「等待完成」,让主线程等待所有子线程执行完毕后,统一处理结果(如统计、汇总)。
三者配合是高并发业务中典型的「限流→同步→汇总」模式,除了秒杀,还可用于:高并发接口测试、批量数据处理、分布式任务执行等场景。
秒杀场景中「限流→同步→汇总」的核心思维是否和企业级高并发场景一致,以及如何基于这个核心思维,用并发工具类做更贴合企业级要求的完善------答案是:核心思维完全一致,但企业级落地会在「容错、监控、性能、扩展性」上做大量完善,而非仅用基础并发工具类堆砌功能。
下面我先拆解企业级高并发秒杀的核心思维,再给出基于并发工具类的企业级增强版实现,让你看到从Demo到生产级的关键优化点。
一、核心思维:企业级高并发秒杀和Demo的底层逻辑完全一致
不管是Demo还是企业级系统,秒杀的核心解决思路都是「分层限流+同步执行+结果汇总」,只是企业级会把每个环节做深、做稳:
| 环节 | Demo级实现 | 企业级落地(核心思维一致,细节更完善) |
|---|---|---|
| 限流 | Semaphore控制线程数 | 多层限流(网关层→应用层→数据库层),Semaphore仅作为应用层限流兜底 |
| 同步齐发 | CyclicBarrier等待线程 | 分布式同步(Redis/ZooKeeper)+ 本地CyclicBarrier,避免单机瓶颈 |
| 结果汇总 | CountDownLatch统计结果 | 分布式计数(Redis原子计数)+ 本地CountDownLatch,支持集群汇总 |
| 核心目标 | 演示功能 | 高可用(99.99%)、低延迟(<100ms)、防超卖/防重复 |
简单说:「Semaphore+CyclicBarrier+CountDownLatch」是企业级思维的"单机版最小闭环",企业级只是把这个闭环扩展到分布式场景,同时补充容错、监控、降级等能力。
二、基于并发工具类的企业级增强版实现(纯Java并发工具+企业级特性)
下面的代码保留「限流→同步→汇总」的核心逻辑,但补充了企业级必备的:超时控制、熔断降级、监控埋点、防超卖、线程池管理、异常兜底,所有增强都基于Java原生并发工具类实现,可直接运行。
企业级秒杀核心代码
java
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
/**
* 企业级秒杀实现:基于Java并发工具类,补充超时、熔断、监控、防超卖等特性
* 核心思维:限流(Semaphore)→ 同步(CyclicBarrier)→ 汇总(CountDownLatch)
*/
public class EnterpriseSeckillDemo {
// ====================== 1. 配置项(企业级:可配置化,而非硬编码) ======================
// 应用层限流:最多10个线程同时处理秒杀请求(根据服务器CPU/内存配置)
private static final Semaphore SECKILL_SEMAPHORE = new Semaphore(10, true);
// 同步齐发:每批秒杀最多20个请求(避免单批线程过多导致CPU飙升)
private static final int BATCH_SIZE = 20;
// 秒杀超时时间:500ms(企业级:避免线程长时间阻塞)
private static final long AWAIT_TIMEOUT = 500;
private static final TimeUnit TIMEOUT_UNIT = TimeUnit.MILLISECONDS;
// 库存配置(企业级:从配置中心/数据库加载)
private static final AtomicInteger STOCK = new AtomicInteger(10);
// 防超卖兜底:库存阈值(避免原子操作也出问题)
private static final int STOCK_THRESHOLD = 0;
// ====================== 2. 监控埋点(企业级:核心指标必须监控) ======================
// 总请求数
private static final AtomicLong TOTAL_REQUEST = new AtomicLong(0);
// 成功数/失败数/超时数
private static final AtomicLong SUCCESS_COUNT = new AtomicLong(0);
private static final AtomicLong FAIL_COUNT = new AtomicLong(0);
private static final AtomicLong TIMEOUT_COUNT = new AtomicLong(0);
// 熔断开关:失败率超过50%触发熔断(避免雪崩)
private static volatile boolean CIRCUIT_BREAKER = false;
private static final double CIRCUIT_THRESHOLD = 0.5;
// ====================== 3. 线程池(企业级:统一管理线程,而非裸线程) ======================
// 秒杀线程池:核心参数根据服务器配置调整(核心线程=CPU核心数,最大线程=2*CPU核心数)
private static final ExecutorService SECKILL_THREAD_POOL = new ThreadPoolExecutor(
Runtime.getRuntime().availableProcessors(),
Runtime.getRuntime().availableProcessors() * 2,
60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000), // 队列缓冲,避免线程创建过多
new ThreadFactory() { // 自定义线程名,便于问题排查
private final AtomicInteger threadNum = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "seckill-thread-" + threadNum.getAndIncrement());
}
},
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略:主线程兜底,避免请求丢失
);
public static void main(String[] args) throws InterruptedException {
System.out.println("===== 企业级秒杀系统启动 =====");
System.out.println("初始库存:" + STOCK.get() + " | 每批处理:" + BATCH_SIZE + "个请求");
// 模拟100个秒杀请求(企业级:请求来自网关/消息队列)
CountDownLatch batchLatch = new CountDownLatch(BATCH_SIZE);
CyclicBarrier seckillBarrier = new CyclicBarrier(BATCH_SIZE, () -> {
// 栅栏回调:秒杀开始前检查熔断
if (CIRCUIT_BREAKER) {
System.out.println("\n===== 触发熔断,本次秒杀批次取消 =====");
return;
}
System.out.println("\n===== 秒杀批次开始!当前库存:" + STOCK.get() + " =====");
});
for (int i = 1; i <= BATCH_SIZE; i++) {
int userId = i;
SECKILL_THREAD_POOL.submit(() -> {
// 监控:总请求数+1
TOTAL_REQUEST.incrementAndGet();
boolean isSuccess = false;
try {
// ========== 步骤1:熔断检查(企业级:前置拦截,避免无效请求) ==========
if (CIRCUIT_BREAKER) {
FAIL_COUNT.incrementAndGet();
System.out.println("用户" + userId + ":触发熔断,秒杀请求被拦截");
return;
}
// ========== 步骤2:应用层限流(Semaphore) ==========
// 带超时的acquire:避免线程永久阻塞(企业级核心)
boolean acquireSuccess = SECKILL_SEMAPHORE.tryAcquire(AWAIT_TIMEOUT, TIMEOUT_UNIT);
if (!acquireSuccess) {
TIMEOUT_COUNT.incrementAndGet();
System.out.println("用户" + userId + ":获取限流许可超时,秒杀失败");
return;
}
// ========== 步骤3:同步齐发(CyclicBarrier + 超时) ==========
try {
// 带超时的await:避免栅栏永久等待
seckillBarrier.await(AWAIT_TIMEOUT, TIMEOUT_UNIT);
} catch (TimeoutException e) {
TIMEOUT_COUNT.incrementAndGet();
System.out.println("用户" + userId + ":等待秒杀开始超时,秒杀失败");
return;
} catch (BrokenBarrierException e) {
FAIL_COUNT.incrementAndGet();
System.out.println("用户" + userId + ":秒杀栅栏被破坏,秒杀失败");
return;
}
// ========== 步骤4:核心秒杀(防超卖+原子操作) ==========
// 双重检查:避免原子操作也出现超卖(企业级防超卖兜底)
if (STOCK.get() > STOCK_THRESHOLD) {
int remainStock = STOCK.decrementAndGet();
if (remainStock >= STOCK_THRESHOLD) {
SUCCESS_COUNT.incrementAndGet();
isSuccess = true;
System.out.println("用户" + userId + ":秒杀成功!剩余库存:" + remainStock);
} else {
// 兜底:库存为负,回滚(避免超卖)
STOCK.incrementAndGet();
FAIL_COUNT.incrementAndGet();
System.out.println("用户" + userId + ":秒杀失败,库存不足(兜底回滚)");
}
} else {
FAIL_COUNT.incrementAndGet();
System.out.println("用户" + userId + ":秒杀失败,库存已售罄");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
FAIL_COUNT.incrementAndGet();
System.out.println("用户" + userId + ":秒杀线程被中断,秒杀失败");
} finally {
// ========== 步骤5:释放资源(企业级:必须finally) ==========
// 仅当获取许可成功时才释放,避免许可泄漏
if (SECKILL_SEMAPHORE.availablePermits() < 10) {
SECKILL_SEMAPHORE.release();
}
// ========== 步骤6:结果汇总(CountDownLatch) ==========
batchLatch.countDown();
// ========== 步骤7:熔断判断(企业级:动态调整) ==========
long total = TOTAL_REQUEST.get();
if (total > 0) {
double failRate = (double) (FAIL_COUNT.get() + TIMEOUT_COUNT.get()) / total;
if (failRate > CIRCUIT_THRESHOLD) {
CIRCUIT_BREAKER = true;
System.out.println("\n===== 失败率超过" + (CIRCUIT_THRESHOLD*100) + "%,触发熔断 =====");
}
}
}
});
}
// 等待本批次秒杀完成
batchLatch.await();
// 关闭线程池(企业级:优雅关闭)
SECKILL_THREAD_POOL.shutdown();
if (!SECKILL_THREAD_POOL.awaitTermination(1, TimeUnit.MINUTES)) {
SECKILL_THREAD_POOL.shutdownNow();
}
// ====================== 4. 企业级结果汇总(含监控指标) ======================
System.out.println("\n===== 秒杀批次结束(企业级监控汇总) =====");
System.out.println("总请求数:" + TOTAL_REQUEST.get());
System.out.println("成功数:" + SUCCESS_COUNT.get());
System.out.println("失败数:" + FAIL_COUNT.get());
System.out.println("超时数:" + TIMEOUT_COUNT.get());
System.out.println("剩余库存:" + STOCK.get());
System.out.println("熔断状态:" + (CIRCUIT_BREAKER ? "已触发" : "正常"));
System.out.println("失败率:" + String.format("%.2f%%",
(double) (FAIL_COUNT.get() + TIMEOUT_COUNT.get()) / TOTAL_REQUEST.get() * 100));
}
}
三、企业级完善的核心优化点(基于并发工具类)
1. 限流环节:从"无脑阻塞"到"可控超时"
- Demo级:
acquire()永久阻塞,线程可能卡死; - 企业级:
tryAcquire(timeout, unit)带超时的获取,超过时间直接失败,避免线程池耗尽; - 额外优化:Semaphore开启公平模式,避免线程饥饿;finally中判断许可是否获取成功,再释放(避免许可泄漏)。
2. 同步环节:从"无限等待"到"超时+熔断"
- Demo级:
await()永久等待,栅栏破坏会导致线程卡死; - 企业级:
await(timeout, unit)带超时的同步,同时增加熔断开关(失败率过高时直接拦截请求); - 额外优化:CyclicBarrier的回调中增加熔断检查,避免无效的秒杀执行。
3. 汇总环节:从"裸线程"到"线程池+优雅汇总"
- Demo级:直接new Thread(),线程无法管理,崩溃会导致汇总失败;
- 企业级:用ThreadPoolExecutor自定义线程池(核心参数适配服务器配置),拒绝策略用CallerRunsPolicy(主线程兜底);
- 额外优化:CountDownLatch汇总后,优雅关闭线程池(shutdown + awaitTermination),避免资源泄漏。
4. 业务层:补充企业级必备的"防超卖+监控"
- 防超卖:双重检查(原子操作前+后),库存为负时回滚,避免超卖;
- 监控埋点:统计总请求、成功/失败/超时数,计算失败率,触发熔断;
- 配置化:所有硬编码参数(限流数、超时时间、批次大小)改为可配置(示例中用常量,实际可从配置中心加载)。
四、分布式场景的扩展(核心思维不变,工具类升级)
如果你的系统是分布式集群(多机器),单纯的本地并发工具类不够用,但核心思维还是「限流→同步→汇总」,只是工具类升级:
| 环节 | 单机场景(Java并发工具) | 分布式场景(企业级主流) |
|---|---|---|
| 限流 | Semaphore | Redis分布式限流(Redisson Semaphore) |
| 同步齐发 | CyclicBarrier | Redis/ZooKeeper分布式屏障(Redisson CyclicBarrier) |
| 结果汇总 | CountDownLatch | Redis原子计数(incr/decr)+ 消息队列汇总 |
| 库存管理 | AtomicInteger | Redis分布式锁 + 原子操作(避免超卖) |
关键结论:不管是单机还是分布式,「先限流控并发、再同步保公平、最后汇总做统计」的核心思维完全一致,Java并发工具类是这个思维的"单机最小实现",企业级只是把这个实现扩展到分布式,并补充容错、监控、降级。
总结
- 核心思维一致:企业级高并发秒杀的底层逻辑就是「限流→同步→汇总」,你用的Semaphore/CyclicBarrier/CountDownLatch正是这个思维的核心载体;
- 企业级完善点:在基础并发工具类上补充「超时控制、熔断降级、线程池管理、防超卖、监控埋点」,避免线程阻塞、资源泄漏、业务异常;
- 分布式扩展:核心思维不变,将本地并发工具类替换为分布式工具(如Redisson),解决集群间的同步/限流问题。
这个增强版代码保留了你的核心思路,同时覆盖了企业级生产环境的核心要求,你可以直接运行,也能基于此扩展到分布式场景。