【多线程安全到高并发实战】

系统解析 Java CompletableFuture 框架下的多线程的用法、线程安全原理、乐观锁、悲观锁、防超卖方案选择指南,以及实际项目中的高并发最佳实践。


一、CompletableFuture 基础认知

1.1 它是什么?

CompletableFuture 是 Java 8 引入的异步编程核心组件,基于 Future 和 CompletionStage 两大接口构建,提供:

  • 异步计算:任务后台执行,不阻塞主线程

  • 链式调用:优雅组合多个异步任务

  • 异常处理:完整的异常捕获与恢复

  • 结果组合:支持合并多个 Future

它是 Java 原生异步任务编排的最佳工具。


1.2 常用创建方式

// 无返回值异步任务
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    System.out.println("执行异步任务");
});

// 有返回值异步任务
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "异步结果");

// 使用自定义线程池
ExecutorService executor = Executors.newFixedThreadPool(10);
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "异步任务", executor);

二、线程安全原理与防超卖方案解析

2.1 CompletableFuture 的线程安全来自哪里?

内部依赖两大策略:

✔ 无锁 CAS(Compare-And-Swap)

volatile Object result;

final boolean internalComplete(Object r) {
    return UNSAFE.compareAndSwapObject(this, RESULT, null, r);
}

意义:结果只会被成功设置一次,后续线程竞争失败。

✔ Treiber Stack(无锁栈)管理回调链

每一次回调注册都通过无锁方式入栈,保证并发安全。

✔ volatile 保证可见性

线程间结果及时可见。

结论:
CompletableFuture 只保证自身状态的线程安全,不保证你业务数据的线程安全。


2.2 防超卖方案全解析(高并发核心问题)

方案一:synchronized(悲观锁)

public synchronized boolean purchase() {
    if (stock > 0) {
        stock--;
        return true;
    }
    return false;
}
  • 优点:实现简单

  • 缺点:吞吐量差,严重阻塞

  • 适用:低并发、小业务


方案二:乐观锁(版本号)

实体类对应数据库 库存表中的version字段,mybatis-plus就会对齐,将version当做版本号处理

@Version
private Integer version;

典型写法:


for (int i = 0; i < 3; i++) {
    Product product = productMapper.selectById(productId);
    if (product.getStock() <= 0) return false;

    product.setStock(product.getStock() - 1);
    int rows = productMapper.updateById(product); // MP 自动判断版本

    if (rows > 0) return true;  // 成功
}
  • 优点:性能好,尤其读多写少

  • 缺点:会出现大量重试

  • 适用:中等并发、冲突少的场景


方案三:数据库原子操作(推荐)

@Update("UPDATE product SET stock = stock - 1 WHERE id = #{id} AND stock > 0")
int deductStockAtomic(Long id);
  • 优点:强一致 + 高性能 + 无锁

  • 缺点:SQL 需自定义

  • 适用:常规秒杀、高并发扣减

这是目前 90% 电商场景的首选方案。


方案四:Redis 原子操作 + 异步落库(极致性能)

Long stock = redisTemplate.opsForValue().decrement(key);
if (stock >= 0) {
    CompletableFuture.runAsync(() -> syncToDatabase(productId, stock));
    return true;
} else {
    redisTemplate.opsForValue().increment(key); // 回滚
    return false;
}
  • 优点:性能极高

  • 缺点:一致性保障复杂

  • 适用:超高并发(双11级别)


2.3 实战场景方案选择

场景推荐方案QPS难度
普通电商乐观锁1k–5k
大促秒杀数据库原子操作5k–10k
超高并发秒杀Redis 原子 + 落库10k+
复杂业务多校验悲观锁或分段锁500–1k

三、CompletableFuture 使用技巧

3.1 循环创建异步任务的正确姿势

错误(闭包问题):


for (int i = 0; i < 10; i++) {
    CompletableFuture.runAsync(() -> System.out.println(i));
}

正确:


for (int i = 0; i < 10; i++) {
    final int id = i;
    futures.add(CompletableFuture.runAsync(() -> System.out.println(id)));
}
CompletableFuture.allOf(futures...).join();


3.2 全面的异常处理

exceptionally:异常恢复

future.exceptionally(ex -> "默认值");

handle:成功/失败统一处理

future.handle((r, ex) -> ex == null ? r : "异常处理");

whenComplete:类似 finally

future.whenComplete((r, ex) -> log.info("结束"));


3.3 多任务组合

thenCompose:前后依赖

getUserAsync(id).thenCompose(user -> getOrderAsync(user));

thenCombine:并行任务合并

u.thenCombine(o, (u1, o1) -> u1 + o1);

allOf:等待全部完成

CompletableFuture.allOf(f1, f2, f3).join();

anyOf:任意一个完成

四、高并发场景最佳实践

4.1 秒杀系统示例


@Transactional
public CompletableFuture<Boolean> purchase(Long pid, Long uid) {
    return CompletableFuture.supplyAsync(() -> {
        int rows = jdbc.update("UPDATE ... stock > 0", pid);
        if (rows > 0) {
            createOrder(uid, pid);
            CompletableFuture.runAsync(() -> sendMsg(uid));
            return true;
        }
        return false;
    }, flashSalePool);
}

4.2 性能优化策略

1. 线程池规划

// CPU 密集
newFixedThreadPool(Runtime.getRuntime().availableProcessors());

// IO 密集
newFixedThreadPool(50);
2. 限流(信号量)

Semaphore sem = new Semaphore(100);

3. 监控 + 降级

五、常见问题解答

Q1:CompletableFuture 自身是否线程安全?
A:是,但它不保证你业务数据的线程安全。

Q2:循环创建 supplyAsync 会产生多线程吗?
A:会。但可能受线程池大小限制。

Q3:一定要 allOf().join() 吗?

  • 需要等待所有结果 ⇒ 必须

  • 后台任务(发通知等)⇒ 不需要

Q4:如何选择锁?

  • 低并发:synchronized

  • 中并发:乐观锁

  • 高并发:数据库原子操作

  • 超高并发:Redis

Q5:程序退出时异步任务未完成?
使用 ShutdownHook:


Runtime.getRuntime().addShutdownHook(new Thread(() -> {
    executor.shutdown();
    executor.awaitTermination(...);
}));

六、总结(高质量可复用)

CompletableFuture 的使用原则

  1. 异步编排交给 CompletableFuture

  2. 线程安全交给数据库原子操作

  3. 闭包变量要处理好

  4. 异常不能静默失败

  5. 线程池必须正确配置

  6. 高并发下推荐 Redis/原子 SQL


实战口诀(优化版)


异步任务用 Future,链式编排 Completable; 线程安全要分层,业务数据自己管; 循环创建要注意,闭包变量别踩坑; 高并发防超卖,原子 SQL 最稳健; 线程池要配置,监控降级别缺省。

🔥 防超卖四大方案(通俗易懂版)

在高并发场景下,“库存扣减”是最容易出现超卖的业务。下面从简单到复杂,讲清四种主流方案的原理、优缺点、以及为什么它们能保证不超卖。


① 乐观锁:大家都抢,但成功的人少(需要重试)

核心思想:
每条数据都有一个 version 版本号。
每个线程读取数据后,都带着一个“旧版本号”去更新。
如果这个版本号和数据库里的当前版本号不一致 ⇒ 说明有其他线程抢先更新过了,该线程更新失败,需要重试。

通俗理解:
每个线程都带着一张写着 “v1” 的票去修改库存:

  • 如果数据库当前版本还是 v1 ⇒ 你赢了,修改成功

  • 如果数据库已经变成 v2 ⇒ 说明别人抢先更新了 ⇒ 你要重新读取再试

你写的内容优化版:

每个线程都读到一个版本号(例如 v1),修改时要求数据库版本仍是 v1,才允许更新成功。
若修改期间,版本号被其他线程更新成 v2,则当前线程必须重新读取(得到 v2)然后再尝试,直到成功。

能保证不超卖吗?
✔ 能,因为每次更新都要求版本一致,否则就失败。
✖ 缺点:大量冲突时重试成本高。


② 悲观锁:一次只能一个人进去处理(性能差)

核心思想:
给数据加锁,其他线程必须等待锁释放。

通俗理解:
就像一个卫生间:

  • A进去后把门反锁

  • B 只能排队等 A 出来

  • A 不出来,所有人都别想进去

你写的内容优化版:

悲观锁会阻塞其他线程:只要 A 线程还在修改,B、C、D 都只能排队等待。如果并发量大,性能会急剧下降。

能保证不超卖吗?
✔ 能
✖ 性能最差,不适合秒杀


③ 数据库原子操作:一条 SQL 解决所有问题(最推荐)

核心思想:
直接利用数据库的原子执行能力,一条 SQL 完成库存扣减,要么成功,要么失败

例如:


UPDATE product SET stock = stock - 1 WHERE id = ? AND stock > 0;

通俗理解:
一个人去仓库拿货:

  • 仓库管理员一次性检查库存是否大于 0

  • 大于 0 ⇒ 给你减一 ⇒ 成功

  • 等于 0 ⇒ 不给你 ⇒ 失败

这个过程 整个是原子的(不可分割),绝不会出现并发读写造成超卖。

你写的内容优化版:

数据库能保证一条 update 语句的原子性,只要条件满足才能扣减成功。如果库存不大于 0,SQL 不会执行,从而天然防止超卖。

能保证不超卖吗?
✔ 绝对能
✔ 性能高
✔ 电商 90% 都用这个
✖ 需要写原生 SQL


④ Redis 原子操作 + 异步落库(极致性能)

核心思想:
Redis 的 DECR 操作是原子的,多个线程同时执行也不会出错。


Long stock = redisTemplate.opsForValue().decrement(key);

执行结果:

  • stock >= 0 ⇒ 秒杀成功(还有库存)

  • stock < 0 ⇒ 超卖了,需要回滚(increment)

你写的内容优化版:

将库存和商品 ID 存成 Redis 的 key-value,例如:
stock:1001 = 50
当用户参与秒杀时,先在 Redis 执行 DECR 进行扣减(原子操作)。

如果扣减成功(值 ≥ 0):
→ 表示抢购成功
→ 然后通过异步线程把数据落库(库存减一、订单加一)

如果扣减失败(值 < 0):
→ Redis 回滚(INCR)
→ 表示抢购失败

为什么性能如此高?
因为 Redis 是纯内存 + 单线程架构,天然保证同一个 key 的访问串行化。

能保证不超卖吗?
✔ Redis 层一定不会超卖
✖ 最终一致性要自己保证(落库失败如何处理?补偿?重试?)


🔔 四大方案极致对比总结

方案是否阻塞是否会超卖性能适用场景
悲观锁✔ 阻塞❌ 不会❌ 最差小系统、低并发
乐观锁❌ 不阻塞❌ 不会⭐ 中等读多写少、中等并发
DB 原子 SQL❌ 不阻塞❌ 不会⭐⭐ 高大部分电商场景
Redis DECR❌ 不阻塞❌ Redis 层不会⭐⭐⭐ 最高超高并发秒杀

📘 最通俗易懂的一句话总结

悲观锁靠“互斥”,乐观锁靠“版本号”,原子 SQL 靠“数据库”,Redis DECR 靠“内存单线程”。
选择哪个,看你业务的 QPS 和一致性需求。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值