Java案例如何实现分布式锁:从原理到实战的完整指南
目录导读
- 为什么需要分布式锁?
- 分布式锁的核心设计原则
- 基于Redis的分布式锁实现(实战案例)
- 基于ZooKeeper的分布式锁实现
- 常见问题与解决方案(Q&A)
- 性能对比与最佳实践
为什么需要分布式锁?
在单体应用中,Java的synchronized或ReentrantLock就能解决线程安全问题,但在微服务架构下,多个服务实例会同时访问共享资源(如数据库、缓存、文件),此时本地锁失效,必须引入分布式锁。

典型场景:
- 电商秒杀:防止超卖
- 定时任务:避免同一任务被多个节点重复执行
- 分布式事务:确保全局唯一性操作
分布式锁的核心设计原则
一个合格的分布式锁必须满足:
- 互斥性:同一时刻只有一个客户端持有锁
- 安全性:不能发生死锁,即使持有锁的客户端崩溃
- 可重入性:同一线程可多次获取同一把锁
- 高可用:锁服务本身不能成为单点故障
基于Redis的分布式锁实现(实战案例)
Redis因其高性能和原子操作特性,成为最流行的分布式锁实现方案。
1 基础版:SETNX + EXPIRE
public class RedisDistributedLock {
private Jedis jedis;
private String lockKey;
private String requestId; // 用于保证锁的释放属于持锁者
private int expireTime = 30000; // 锁自动过期时间(毫秒)
public boolean tryLock() {
// SET key value NX PX 30000 原子操作
String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
return "OK".equals(result);
}
public void unlock() {
// 使用Lua脚本保证原子性:先判断锁是否属于自己再删除
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) else return 0 end";
jedis.eval(script, Collections.singletonList(lockKey),
Collections.singletonList(requestId));
}
}
核心要点:
- 使用
requestId(UUID)防止误删其他线程的锁 - Lua脚本保证
get和del的原子性 - 设置过期时间防止死锁
2 进阶:Redisson实现
生产环境推荐使用Redisson框架,它封装了复杂的逻辑:
// 添加依赖:org.redisson:redisson-spring-boot-starter
@Autowired
private RedissonClient redissonClient;
public void businessMethod() {
RLock lock = redissonClient.getLock("myLock");
try {
// 尝试获取锁,最多等待10秒,锁30秒后自动释放
if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
// 执行临界区代码
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
Redisson优势:
- 内置看门狗机制:自动续期,避免业务超时导致锁自动释放
- 支持可重入、公平锁、读写锁等高级特性
- 高可用:支持Redis集群和哨兵模式
基于ZooKeeper的分布式锁实现
ZooKeeper通过临时顺序节点实现分布式锁,原理更可靠。
public class ZkDistributedLock {
private ZooKeeper zk;
private String lockPath = "/locks/";
public boolean tryLock() {
// 创建临时顺序节点
String currentPath = zk.create(lockPath + "lock-",
new byte[0],
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL_SEQUENTIAL);
// 获取所有子节点,判断自己是否最小序号
List<String> children = zk.getChildren("/locks", false);
Collections.sort(children);
if (currentPath.equals("/locks/" + children.get(0))) {
return true; // 成功获取锁
}
// 否则监听前一个节点
String prevNode = children.get(children.indexOf(currentPath) - 1);
zk.exists("/locks/" + prevNode, watcher);
return false;
}
}
ZK锁特点:
- 强一致性:ZAB协议保证,比Redis更可靠
- 无需设置过期时间,客户端断开自动释放
- 性能低于Redis,适合对一致性要求极高的场景
常见问题与解决方案(Q&A)
Q1:Redis锁过期后业务还没执行完怎么办?
A:使用Redisson的看门狗机制,它会自动延长锁的过期时间(默认每10秒续期一次),或者手动实现Timer任务进行续期。
Q2:如何避免Redis主从切换导致的锁丢失? A:使用RedLock算法,向多个独立Redis节点同时加锁,超过半数成功才认为加锁成功,但RedLock本身存在争议,更推荐:升级到Redis 7.0+,使用基于复制日志的WAIT命令。
Q3:ZK分布式锁为什么比Redis更可靠? A:ZK的临时节点机制保证:如果客户端崩溃,节点自动删除,锁立即释放,而Redis的锁依赖于过期时间,存在时间差,但ZK性能较低,每秒只能处理几千次加锁。
Q4:分布式锁的超时时间设置多少合适? A:根据业务最大执行时间估算,正常业务2秒,最大超时5秒,设置锁过期时间为5秒,同时配合自动续期机制(如Redisson的watchdog)增加容错。
性能对比与最佳实践
| 方案 | 性能 | 可靠性 | 适用场景 |
|---|---|---|---|
| Redis SETNX | 高(10万+ TPS) | 中等(可能丢失) | 秒杀、缓存操作 |
| Redisson | 高(5万+ TPS) | 较高(自动续期) | 复杂业务、高并发 |
| ZooKeeper | 低(3千+ TPS) | 极高(强一致) | 配置中心、选举 |
终极建议:
- 90%的场景选择Redisson + Redis集群,它完美平衡了性能和可靠性
- 对数据一致性要求严格的金融场景,选择ZooKeeper
- 避免自己造轮子,使用成熟的分布式锁中间件
通过本篇Java案例,相信您已经掌握了分布式锁的核心实现方式,在实际开发中,请根据业务场景选择最合适的方案,并始终牢记:分布式锁是解决并发问题的最后手段,能用无锁设计优化则更优。