miqiu的分布式锁二:实战——用JMeter验证JVM锁能否解决MySQL超卖问题
实验背景
在秒杀场景中,超卖问题是典型的并发编程挑战。本文通过JMeter压测工具,验证基于JVM的两种锁机制(synchronized/ReentrantLock)对MySQL库存操作的防护效果。
实验一:内存库存操作验证
1.1 无锁场景
public void deduct() {stock.setStock(stock.getStock() - 1);System.out.println("库存余量:" + stock.getStock());
}
压测结果(100线程×50次循环):
- 平均响应时间:3ms
- 吞吐量:2217/sec
- 最终库存:-89(严重超卖)
1.2 synchronized锁方案
public synchronized void deduct() {stock.setStock(stock.getStock() - 1);System.out.println("库存余量:" + stock.getStock());
}
压测结果对比:
- 平均响应时间 ↗ 25ms(733%增长)
- 吞吐量 ↘ 396/sec(82%下降)
- 最终库存 ✅ 0(完美解决)
1.3 ReentrantLock方案
private final ReentrantLock lock = new ReentrantLock();public void deduct() {lock.lock();try {stock.setStock(stock.getStock() - 1);System.out.println("库存余量:" + stock.getStock());} finally {lock.unlock();}
}
性能表现:
- 平均响应时间:22ms
- 吞吐量:440/sec
- 最终库存 ✅ 0
实验二:真实MySQL库存操作
2.1 无锁数据库操作
public void deduct() {Stock stock = stockMapper.selectByProductCode("1001");if(stock != null && stock.getCount() > 0) {stock.setCount(stock.getCount() - 1);stockMapper.updateById(stock);}
}
压测结果:
- 平均响应时间:249ms
- 吞吐量:394/sec
- 最终库存:4900(严重超卖)
2.2 ReentrantLock防护方案
private final ReentrantLock lock = new ReentrantLock();public void deduct() {lock.lock();try {Stock stock = stockMapper.selectByProductCode("1001");if(stock != null && stock.getCount() > 0) {stock.setCount(stock.getCount() - 1);stockMapper.updateById(stock);}} finally {lock.unlock();}
}
验证结果:
- 平均响应时间 ↗ 623ms(151%增长)
- 吞吐量 ↘ 158/sec(60%下降)
- 最终库存 ✅ 0(正确扣减)
关键结论
-
防护有效性
JVM级锁能有效解决单机部署下的超卖问题,确保库存操作的原子性 -
性能代价
synchronized/ReentrantLock均造成吞吐量显著下降,响应时间成倍增加 -
架构局限
- 仅适用于单服务实例场景
- 分布式部署时不同JVM实例的锁相互不可见
- 数据库连接池耗尽风险(长时间持锁)