Java分布式锁实现方式详解
作者:MadeInSQL
本文详细介绍了Java分布式锁的概念、实现方式、关键问题、最佳实践及常见问题与解决方案,感兴趣的朋友跟随小编一起看看吧

Java分布式锁详解
1. 分布式锁的概念
分布式锁是一种在分布式系统中协调多个进程/服务对共享资源进行互斥访问的机制。在单机系统中,我们可以使用Java内置的锁机制(如synchronized、ReentrantLock)来实现线程同步,但在分布式环境下,这些本地锁机制无法跨JVM工作,因此需要分布式锁。
典型应用场景包括:
- 防止重复订单提交
- 秒杀系统中的库存扣减
- 定时任务的分布式调度
- 分布式环境下的缓存更新
2. 分布式锁的实现方式
2.1 基于数据库的实现
实现原理: 利用数据库的唯一性约束或行锁特性实现分布式锁。常见方式包括:
- 创建锁表,利用唯一索引防止重复获取锁
- 使用
SELECT ... FOR UPDATE语句锁定记录
示例代码:
// 基于MySQL实现的分布式锁
public class DatabaseDistributedLock {
private DataSource dataSource;
public boolean tryLock(String lockName, long timeout) {
try (Connection conn = dataSource.getConnection()) {
conn.setAutoCommit(false);
// 使用FOR UPDATE加行锁
PreparedStatement stmt = conn.prepareStatement(
"SELECT * FROM distributed_lock WHERE lock_name = ? FOR UPDATE");
stmt.setString(1, lockName);
ResultSet rs = stmt.executeQuery();
if (!rs.next()) {
// 锁不存在则插入
PreparedStatement insertStmt = conn.prepareStatement(
"INSERT INTO distributed_lock(lock_name, owner, create_time) VALUES (?, ?, ?)");
insertStmt.setString(1, lockName);
insertStmt.setString(2, Thread.currentThread().getName());
insertStmt.setTimestamp(3, new Timestamp(System.currentTimeMillis()));
insertStmt.executeUpdate();
}
conn.commit();
return true;
} catch (SQLException e) {
return false;
}
}
}优缺点:
- 优点:实现简单,无需额外中间件
- 缺点:性能较差(数据库I/O开销大),非阻塞锁实现复杂,存在单点故障风险
2.2 基于Redis的实现
实现原理: 利用Redis的SETNX(SET if Not eXists)命令实现互斥性,通过设置过期时间防止死锁。
RedLock算法(Redis官方推荐的分布式锁实现):
- 获取当前时间(毫秒)
- 依次尝试从N个独立的Redis节点获取锁
- 计算获取锁消耗的总时间(小于锁超时时间)且获取到大多数节点(N/2+1)的锁才算成功
- 锁的实际有效时间 = 初始有效时间 - 获取锁消耗的时间
- 如果获取锁失败,则向所有节点发送释放锁命令
示例代码(使用Redisson客户端):
// 使用Redisson实现分布式锁
public class RedisDistributedLockExample {
public void doWithLock() {
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
RLock lock = redisson.getLock("myLock");
try {
// 尝试获取锁,最多等待100秒,锁定后10秒自动释放
boolean isLocked = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (isLocked) {
// 执行业务逻辑
System.out.println("Lock acquired, doing business logic...");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
redisson.shutdown();
}
}
}Redis分布式锁的关键问题:
- 原子性:使用Lua脚本确保SETNX和EXPIRE操作的原子性
- 锁续期:通过守护线程定期检查并延长锁的有效期
- 锁释放:确保只有锁的持有者才能释放锁(value中存储唯一标识)
- 集群容错:主从切换可能导致锁丢失,RedLock算法提供了一定解决方案
2.3 基于Zookeeper的实现
实现原理: 利用Zookeeper的临时顺序节点和Watch机制实现分布式锁。
实现步骤:
- 在指定路径下创建临时顺序节点
- 获取父节点下的所有子节点并排序
- 判断当前节点是否为序号最小的节点:
- 是则获取锁成功
- 否则对前一个节点注册Watcher
- 锁释放后,Zookeeper会通知下一个等待的节点
示例代码(使用Curator框架):
public class ZookeeperDistributedLock {
private CuratorFramework client;
private InterProcessMutex lock;
public ZookeeperDistributedLock(String connectString, String lockPath) {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
client = CuratorFrameworkFactory.newClient(connectString, retryPolicy);
client.start();
lock = new InterProcessMutex(client, lockPath);
}
public void doWithLock(Runnable task) throws Exception {
try {
// 获取锁,最多等待5秒
if (lock.acquire(5, TimeUnit.SECONDS)) {
task.run();
}
} finally {
if (lock.isAcquiredInThisProcess()) {
lock.release();
}
}
}
}Zookeeper锁的特点:
- 可靠性高:基于CP模型,数据一致性有保障
- 自动释放:会话结束或中断时临时节点自动删除
- 公平锁:按照节点创建顺序获取锁
- 性能较好:相比数据库方案,但比Redis略低
3. 分布式锁的关键特性
一个完善的分布式锁实现应具备以下特性:
- 互斥性:同一时刻只有一个客户端能持有锁
- 避免死锁:锁必须有超时机制或自动释放机制
- 容错性:即使部分节点故障,锁服务仍然可用
- 可重入性:同一个客户端可以多次获取同一把锁
- 高性能:获取和释放锁的操作要高效
- 公平性:获取锁的顺序与请求顺序一致(可选)
4. 分布式锁的最佳实践
锁粒度的选择:
- 细粒度锁:资源竞争精准,但管理复杂
- 粗粒度锁:实现简单,但并发度低
超时设置:
- 获取锁的超时时间:避免长时间等待
- 锁持有的超时时间:业务操作应在该时间内完成
锁的释放:
- 必须放在finally块中确保释放
- 实现锁的可重入性时要正确维护计数器
异常处理:
- 网络分区时的处理策略
- 锁服务不可用时的降级方案
监控与告警:
- 监控锁的获取成功率
- 监控锁的平均持有时间
5. 常见问题与解决方案
问题1:锁提前过期
- 现象:业务操作未完成锁已过期
- 解决方案:实现锁续期机制(watch dog)
问题2:错误释放他人锁
- 现象:A客户端释放了B客户端的锁
- 解决方案:锁value中存储唯一标识,释放时验证
问题3:网络分区导致脑裂
- 现象:多个客户端同时持有锁
- 解决方案:使用fencing token机制
问题4:锁不可重入
- 现象:同一线程多次获取锁导致死锁
- 解决方案:维护持有者线程和计数器
6. 总结与选型建议
- 数据库锁:适合并发量低、可靠性要求不高、已有数据库环境的场景
- Redis锁:适合高性能、高可用场景,但对一致性要求不能太高
- Zookeeper锁:适合强一致性、高可靠场景,但性能相对较低
实际项目中,推荐使用成熟的框架如Redisson或Curator,它们已经处理了各种边界条件和异常情况。对于关键业务,可以考虑结合多种实现方式提高可靠性。
到此这篇关于Java分布式锁详解的文章就介绍到这了,更多相关Java分布式锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
