高并发秒杀需将锁粒度下沉至商品ID级行锁,推荐优先用ConcurrentHashMap+CAS无锁扣减;中高并发可选分段锁;集群场景必须用Redis分布式锁+Lua脚本,再辅以数据库乐观锁和唯一约束兜底。

在高并发秒杀场景中,不能对整个商品类(比如 Product 类或其 Service 实例)加锁,否则会严重串行化请求,失去并发意义。核心思路是:**把锁的粒度从“类/方法”下沉到“单个商品 ID 对应的库存记录”**,即实现基于商品 ID 的行级锁(逻辑行锁),而非数据库行锁本身(那属于持久层优化)。Java 中可通过多种方式模拟“细粒度库存行锁”,关键在于避免全局锁、减少锁竞争、保证原子性。
用 ConcurrentHashMap + CAS 实现无锁化库存扣减
这是最轻量、推荐优先尝试的方式。不显式加锁,而是依赖 ConcurrentHashMap 存储各商品的当前可售库存(或状态标记),结合 AtomicInteger 或 LongAdder 做原子扣减:
- 初始化时为每个商品 ID 创建一个
AtomicInteger stock = new AtomicInteger(initStock),存入ConcurrentHashMap - 秒杀时调用
stock.compareAndSet(expected, expected - 1),仅当当前值等于预期值才扣减成功 - 失败则重试或返回“库存不足”,无需阻塞等待
- 适合库存量不大、热点商品不多的场景;若极端热点(如单商品每秒数万请求),CAS 失败率升高,需配合限流或降级
用分段锁(Lock Striping)按商品 ID 哈希分桶加锁
当需要更严格的顺序控制(例如要同步更新缓存+DB、或执行复杂校验逻辑),可将锁按商品 ID 分片,避免所有请求争抢同一把锁:
- 预定义固定数量的锁对象数组,如
private final Lock[] locks = new ReentrantLock[64] - 根据商品 ID 计算哈希槽位:
int idx = (int)(productId % locks.length),再对locks[idx]加锁 - 同一商品 ID 总是落在同一个槽位,保证该商品的操作串行;不同商品大概率落在不同槽位,并发互不影响
- 注意锁数组大小需合理(太小仍易冲突,太大浪费内存),建议取 2 的幂次便于取模优化
用 Redis 分布式锁 + Lua 脚本保障跨 JVM 库存一致性
单机锁无法解决集群部署下的并发问题。此时需借助 Redis 实现分布式“商品行锁”:
在调试 Java 后端 API 故障时使用此技能,尤其适用于以下任务:分析日志、跨 Controller、Service 和 Mapper 层追踪请求、定位异常根因、检查 SQL 执行或参数流转,以及生成简洁的诊断结论与修复方案。
- 锁 Key 设计为
seckill:lock:{productId},TTL 设置合理(如 5~10 秒),防止死锁 - 使用
SET key value NX EX seconds命令获取锁,value 为唯一请求标识(如 UUID) - 库存扣减逻辑用 Lua 脚本原子执行:先查 Redis 中剩余库存,大于 0 则
DECR并返回结果,避免查-改非原子问题 - 释放锁时严格校验 value 是否匹配,防止误删他人锁(可用 Redisson 的
RLock封装简化)
数据库层面配合:乐观锁 + 唯一约束兜底
即使应用层做了细粒度锁,最终落库仍需防超卖。不能只靠 SELECT + UPDATE,而应:
- 库存字段增加版本号(
version)或时间戳,在 UPDATE 语句中校验:UPDATE item SET stock = stock - 1, version = version + 1 WHERE id = ? AND stock > 0 AND version = ? - 或直接用库存数值做条件:
WHERE id = ? AND stock >= 1,并检查updateCount == 1 - 订单表加
UNIQUE(product_id, user_id)约束,拦截同一用户重复下单(秒杀防刷) - 数据库唯一索引和乐观锁是最后一道防线,必须有,但不应作为主要并发控制手段
细粒度库存行锁的本质不是“锁住某行数据”,而是“让同一商品的所有并发请求排队执行”。Java 层可通过无锁原子操作、分段锁、分布式锁三种策略分级应对,再叠加数据库乐观锁与唯一约束,形成多层防护。选型取决于系统规模、一致性要求和运维成本——中小流量优先 CAS,中高并发用分段锁,全集群场景必须引入 Redis 锁+Lua 原子脚本。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










