miqiu的分布式(三):JVM本地锁失效的三大典型场景
实验背景
在单机环境下,synchronized和ReentrantLock能有效控制并发。但当系统复杂度升级时,JVM本地锁会面临失效风险。本文通过三个实战案例,揭示本地锁失效的深层原因。
场景一:多例模式下的锁失效
1.1 问题现象
采用Prototype作用域创建多实例服务时,库存扣减出现异常:
@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class StockService {private final ReentrantLock lock = new ReentrantLock();public void deduct() {lock.lock();try {// 数据库操作逻辑} finally {lock.unlock();}}
}
压测结果:
- 初始库存:5000
- 5000次请求后剩余:4904
- 理论应扣减:5000 → 实际丢失96次扣减
1.2 原理分析
关键因素 | 影响说明 |
---|---|
实例隔离 | 每个请求创建独立Service实例 |
锁对象不共享 | 各实例维护自己的ReentrantLock |
并发控制失效 | 实际形成多把锁并行工作 |
场景二:事务管理引发的锁失效
2.1 事务代码示例
@Transactional
public void deduct() {lock.lock();try {// 数据库查询+更新操作} finally {lock.unlock();}
}
测试结果:
- 初始库存:5000
- 压测后剩余:4863
- 丢失137次有效扣减
2.2 事务隔离机制的影响
隔离级别 | 是否可能丢失更新 |
---|---|
READ_COMMITTED | ✔️ |
REPEATABLE_READ | ✔️ |
SERIALIZABLE | ❌ |
规避建议:
- 使用
SELECT ... FOR UPDATE
悲观锁 - 采用版本号乐观锁机制
- 调整事务边界(将锁操作移到事务外部)
场景三:集群部署的局限性
3.1 实验架构
环境配置:
- Nginx反向代理
- 双节点服务集群
- 共享MySQL数据库
3.2 压测结果
指标 | 数值 |
---|---|
请求总量 | 2000次 |
理论扣减量 | 2000 |
实际扣减量 | 1386 |
数据丢失率 | 30.7% |
3.3 分布式锁必要性
关键矛盾点:
- 锁作用域限制在单个JVM内
- 多节点间无锁状态感知
- 数据库连接池可能成为新瓶颈
避坑指南
失效场景 | 核心矛盾 | 解决方案 |
---|---|---|
多例模式 | 锁实例不唯一 | 改用单例模式+静态锁 |
事务管理 | 隔离级别导致锁延迟 | 缩小事务范围或使用数据库锁 |
集群部署 | 跨JVM锁不可见 | 采用分布式锁中间件 |
终极方案建议:
- Redis分布式锁(AP模型优先)
- ZooKeeper分布式锁(CP模型优先)
- 数据库行锁/乐观锁(轻量级方案)