Java分布式锁案例怎么实操?从理论到代码全解析
📚 目录导读
- 分布式锁为什么是必选项?
- 核心原理:Redis、ZooKeeper、数据库三种方案对比
- 实战案例一:基于Redis的分布式锁(含Redisson优雅实现)
- 实战案例二:基于ZooKeeper的分布式锁(含Curator递归锁)
- 实战案例三:基于数据库的分布式锁(含悲观锁与乐观锁)
- 常见问题与错误规避
- 高频问答集(面试必问)
分布式锁为什么是必选项?
在多实例部署的微服务架构中,传统的synchronized或ReentrantLock只能锁住单个JVM进程,当出现库存超卖、重复扣费、定时任务重复执行等场景时,必须引入分布式锁来协调跨节点资源互斥访问。

一个真实案例:某电商促销活动,由于未使用分布式锁,并发下单导致库存从100减到-23,直接经济损失超10万元。
核心原理对比表
| 实现方式 | 优点 | 缺点 | 典型场景 |
|---|---|---|---|
| Redis | 高性能、支持超时自动释放 | 主从切换可能丢锁(需Redlock) | 高并发扣减库存 |
| ZooKeeper | 强一致性、自动释放(临时节点) | 性能较低、避免脑裂 | 配置中心、选主场景 |
| 数据库 | 实现简单、无需额外组件 | 性能瓶颈、死锁风险 | 低并发、遗留系统改造 |
实战案例一:基于Redis的分布式锁
1 基础版(不推荐生产使用)
// 错误示范:非原子操作
if (jedis.setnx(key, value) == 1) {
jedis.expire(key, 30); // 可能setnx成功但expire失败导致死锁
}
2 正确版(Lua脚本保证原子性)
-- 加锁
EVAL "return redis.call('set',KEYS[1],ARGV[1],'NX','PX',ARGV[2])" 1 lockKey uuid 30000
-- 解锁(必须校验身份)
EVAL "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end" 1 lockKey uuid
3 生产级实现:Redisson框架
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.27.1</version>
</dependency>
@Autowired
private RedissonClient redissonClient;
public void seckill(Long productId) {
RLock lock = redissonClient.getLock("lock:product:" + productId);
try {
// 等待100秒,锁30秒自动释放(看门狗自动续期)
if (lock.tryLock(100, 30, TimeUnit.SECONDS)) {
// 业务逻辑
stockService.deduct(productId);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
关键点:Redisson的lock()方法自带自动续期机制(Watch Dog),每10秒检查一次,避免业务超时锁自动释放。
实战案例二:基于ZooKeeper的分布式锁
1 原生写法(不推荐)
// ZK API写临时顺序节点,监听前一个节点删除事件 // 代码量巨大,且需要处理连接丢失重连
2 Curator框架实现(推荐)
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.5.0</version>
</dependency>
@Bean
public CuratorFramework curatorFramework() {
CuratorFramework client = CuratorFrameworkFactory.newClient("127.0.0.1:2181",
new ExponentialBackoffRetry(1000, 3));
client.start();
return client;
}
public void createOrder() {
InterProcessMutex lock = new InterProcessMutex(curatorFramework, "/lock/order");
try {
if (lock.acquire(10, TimeUnit.SECONDS)) {
// 生成订单业务
orderService.create();
}
} catch (Exception e) {
log.error("获取ZK锁失败", e);
} finally {
try {
lock.release();
} catch (Exception e) {
log.error("释放锁失败", e);
}
}
}
对比Redis:ZK锁没有过期时间风险(连接断开自动删除临时节点),适合对一致性要求极高的场景。
实战案例三:基于数据库的分布式锁
1 悲观锁(for update)
-- 需要innodb引擎 + 事务
SELECT * FROM product WHERE id = #{productId} FOR UPDATE;
-- 更新库存
UPDATE product SET stock = stock - 1 WHERE id = #{productId};
2 乐观锁(版本号机制)
UPDATE product
SET stock = stock - 1, version = version + 1
WHERE id = #{productId} AND version = #{oldVersion}
适用条件:低并发、数据库是唯一存储载体、不能引入Redis/ZK时使用。
常见问题与错误规避
- ❌ 忘记设置超时时间:导致锁永远不释放
- ❌ 锁误删:使用
uuid作为value,解锁时校验身份 - ❌ Redis主从切换丢锁:高安全场景使用Redlock或ZK
- ❌ 业务未实现幂等:锁只能保证互斥,不能保证业务幂等
关键问题:锁的粒度要细,比如按用户ID+商品ID做锁,避免锁范围过大导致性能下降。
高频问答集
Q: 分布式锁的超时时间如何设置? A: 根据业务最大执行时间 + 20%缓冲,Redisson看门狗机制可自动续期,推荐使用。
Q: 如果业务执行时间超过锁超时时间怎么办?
A: 两种方案:1)使用Redisson看门狗自动续期;2)业务逻辑内定期调用expire延长锁时间。
Q: ZooKeeper和Redis谁更好? A: 高并发选Redis(如万级QPS扣库存),强一致选ZK(如分布式选举、配置变更)。
Q: 如何测试分布式锁的正确性? A: 多线程并发压测,验证:1)同一时刻只有一个线程进入 2)异常中断后锁自动释放 3)高并发下无死锁现象。
分布式锁实战的核心在于:
- 选型:根据一致性需求和性能要求选择Redis/ZK/数据库
- 原子性:避免setnx+expire非原子操作
- 安全性:必须校验锁持有者,避免误释放
- 容错:设计超时释放和自动续期机制
推荐生产直接使用Redisson或Curator框架,避免重复造轮子,务必在压测环境中验证锁的性能与正确性,这是分布式系统稳定运行的基石。