Java案例如何实现分布式锁?从原理到实战全解析
📚 文章导读目录
- 为什么需要分布式锁? —— 业务场景与痛点分析
- 分布式锁的核心要求 —— 互斥、安全、高可用缺一不可
- 基于Redis的分布式锁实现(Redlock算法) — 最主流方案详解
- 基于ZooKeeper的分布式锁实现 —— 强一致性场景首选
- 基于数据库的分布式锁实现 —— 轻量级但需谨慎
- 三大实现方案对比与选型建议 —— 你的场景该用哪种?
- 常见问题与避坑指南(FAQ) —— 面试高频考点与实战陷阱
- 总结与未来趋势 —— 从分布式锁到无锁化设计
为什么需要分布式锁?
在单体应用中,我们使用 synchronized 或 ReentrantLock 就能解决线程安全问题,但在分布式系统中,多个JVM实例同时访问共享资源(如数据库记录、文件、缓存)时,本地锁完全失效。

- 秒杀场景:多个服务节点同时扣减库存,导致超卖。
- 定时任务:多个节点同时执行同一任务,造成重复处理。
- 数据一致性:多实例并发修改同一行数据库记录,引发脏写。
核心矛盾:分布式环境下,资源竞争从“线程级”上升为“进程级”,必须有一个跨JVM的协调者来保证互斥访问,这就是分布式锁的价值所在。
分布式锁必须满足的四大特性
| 特性 | 说明 | 反例 |
|---|---|---|
| 互斥性 | 同一时刻只能一个客户端持有锁 | 两个节点同时获得锁 |
| 安全性 | 锁不会被其他客户端释放(无锁误删) | 锁超时后被其他线程释放 |
| 可用性 | 锁服务宕机后仍能正常工作(主从切换) | Redis主节点宕机后锁丢失 |
| 可重入性 | 同一线程可多次获取同一把锁 | 递归调用时发送死锁等待 |
基于Redis的分布式锁实现(案例+代码)
1 基础版:SETNX + EXPIRE(不推荐)
public boolean tryLock(String key, String value, long timeout) {
// 原子操作:如果key不存在则设置,并同时设置过期时间
Boolean result = redisTemplate.opsForValue()
.setIfAbsent(key, value, timeout, TimeUnit.SECONDS);
return Boolean.TRUE.equals(result);
}
public void unlock(String key, String value) {
// 释放锁时需校验持有者:避免误删别人的锁
String currentValue = redisTemplate.opsForValue().get(key);
if (value.equals(currentValue)) {
redisTemplate.delete(key);
}
}
致命缺陷:
- 锁误删:A线程锁超时自动释放,B线程获取锁,A线程执行完调用
unlock删除了B的锁。 - 不可重入:同线程二次加锁会失败。
- 主从异步复制:主节点宕机后,从节点还未同步锁数据,导致锁丢失。
2 进阶版:Redlock算法(生产级方案)
核心思想:同时向N个独立Redis节点(通常是5个)申请锁,超过半数(N/2+1)成功才算获得锁。
public class RedissonDistributedLock {
private RedissonClient redissonClient;
public boolean tryRedLock(String key, long waitTime, long leaseTime) {
RLock lock = redissonClient.getLock(key);
try {
// waitTime:最多等待时间,leaseTime:锁自动释放时间
return lock.tryLock(waitTime, leaseTime, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
return false;
}
}
public void releaseRedLock(String key) {
RLock lock = redissonClient.getLock(key);
lock.unlock();
}
}
Redlock关键机制:
- 看门狗(WatchDog):自动续期锁过期时间,防止业务未完成锁被释放。
- Redisson封装的RLock:实现了可重入、公平锁、读写锁等高级特性。
适用场景:对锁安全性要求极高(如金融交易),且愿意接受一定性能开销。
基于ZooKeeper的分布式锁实现
1 基本原理
利用ZooKeeper的临时有序节点特性:
- 客户端在锁路径下创建临时有序节点。
- 获取当前路径下所有子节点,判断自己是否为序号最小的那个。
- 如果是,获得锁;否则,监听前一个节点的删除事件。
- 释放锁时,删除自身节点(客户端断开连接时自动删除)。
2 代码示例(基于Curator框架)
public class ZkDistributedLock {
private CuratorFramework client;
public boolean tryZkLock(String path, long timeout) throws Exception {
InterProcessMutex lock = new InterProcessMutex(client, path);
return lock.acquire(timeout, TimeUnit.SECONDS);
}
public void releaseZkLock(String path) throws Exception {
InterProcessMutex lock = new InterProcessMutex(client, path);
lock.release();
}
}
优点:
- 强一致性(ZAB协议保证)。
- 无锁过期时间问题(临时节点随客户端断开自动删除)。
- 天然的公平锁(节点有序性保证)。
缺点:
- 性能较低(每次锁操作需磁盘写入+Leader选举)。
- 节点数量多时,Watcher回调风暴可能导致服务压力。
基于数据库的分布式锁实现(MySQL为例)
1 乐观锁(CAS思想)
@Update("UPDATE product SET stock = stock - 1 WHERE id = #{productId} AND stock > 0")
boolean deductStock(Long productId);
原理:利用数据库行锁,更新时检查版本号或库存条件。
缺点:
- 死锁风险(需合理设计索引)。
- 数据库连接池压力大。
- 不适合高并发场景(磁盘IO瓶颈)。
2 悲观锁(for update)
SELECT * FROM table WHERE condition FOR UPDATE; -- 业务逻辑处理... COMMIT;
使用限制:
- InnoDB引擎才支持行锁。
- 必须走索引,否则退化为表锁。
- 连接需手动开启事务。
三大方案对比与选型建议
| 维度 | Redis | ZooKeeper | 数据库 |
|---|---|---|---|
| 性能 | 高(10万+QPS) | 中(数千QPS) | 低(千级QPS) |
| 可靠性 | AP(最终一致性) | CP(强一致性) | CP(强一致性) |
| 自动续期 | 需额外实现 | 临时节点自动释放 | 无 |
| 可重入 | 需特殊实现 | 支持 | 支持 |
| 学习成本 | 低+Redisson封装 | 中(Curator封装) | 低 |
| 推荐场景 | 高并发、可接受短暂锁丢失 | 强一致性、关键资源 | 小规模、低并发 |
选型铁律:
- 99%高并发业务 → Redis + Redlock。
- 资金/交易类强一致性 → ZooKeeper。
- 资源极小且无Redis/ZK → 数据库乐观锁。
常见问题与避坑指南(FAQ)
❓ Q1:锁超时后业务还没执行完怎么办?
答案:
- Redis方案:开启Redisson WatchDog自动续期。
- ZK方案:临时节点自动释放,业务会抛出异常需设计幂等重试。
- 数据库方案:无内置续期,需在代码中定时刷新“锁记录”过期时间。
❓ Q2:主从切换导致的锁丢失如何解决?
答案:
- 终极方案:使用Redlock多节点写入。
- 降级方案:使用Redis Cluster模式,但依然不能100%避免。
- 业务兜底:对写操作设计幂等性(如唯一索引、版本号)。
❓ Q3:如何实现“可重入”分布式锁?
答案:
- Redis:记录锁持有者线程ID,每次加锁时判断是否为同一线程。
- ZK:Curator的
InterProcessMutex天然支持。 - 数据库:在锁表中增加
thread_id字段。
❓ Q4:分布式锁中的“死锁”如何避免?
答案:
- 设置锁自动过期时间(Redis的
EXPIRE)。 - 使用ZK临时节点。
- 确保解锁操作放在
finally块中。
总结与未来趋势
分布式锁的本质是分布式共识问题的一种简化实现,本文通过Redis、ZooKeeper、数据库三个维度的案例,完整呈现了从原理到代码的落地过程,核心要点包括:
- 选择正确的中间件:Redis适合高并发,ZK适合强一致性,数据库适合简单场景。
- 注意边界情况:锁超时、主从切换、网络分区,必须在设计阶段考虑。
- Redisson/Curator:成熟的客户端库能规避90%的坑。
2 未来趋势
- 无锁化设计:利用Redis原子操作(如
INCR)、消息队列顺序消费代替锁。 - 多级锁融合:本地锁+分布式锁(如Caffeine+Redis)减少网络开销。
- 云原生架构:分布式锁被封装为PaaS服务(如阿里云Tair、腾讯云CKV)。
终极建议:任何分布式锁都不是银弹,优先思考能否用“幂等设计”和“最终一致性”简化问题,如果必须加锁,请从最轻量的方案开始,并在业务逻辑层做好容错。
参考资料(可忽略):
- Redisson官方文档
- Apache Curator官方示例
- 《分布式系统常用技术及案例分析》第5章