Java分布式锁实现方式详解

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

Java分布式锁详解

1. 分布式锁的概念

分布式锁是一种在分布式系统中协调多个进程/服务对共享资源进行互斥访问的机制。在单机系统中,我们可以使用Java内置的锁机制(如synchronizedReentrantLock)来实现线程同步,但在分布式环境下,这些本地锁机制无法跨JVM工作,因此需要分布式锁。

典型应用场景包括:

  • 防止重复订单提交
  • 秒杀系统中的库存扣减
  • 定时任务的分布式调度
  • 分布式环境下的缓存更新

2. 分布式锁的实现方式

2.1 基于数据库的实现

实现原理: 利用数据库的唯一性约束或行锁特性实现分布式锁。常见方式包括:

  1. 创建锁表,利用唯一索引防止重复获取锁
  2. 使用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官方推荐的分布式锁实现):

  1. 获取当前时间(毫秒)
  2. 依次尝试从N个独立的Redis节点获取锁
  3. 计算获取锁消耗的总时间(小于锁超时时间)且获取到大多数节点(N/2+1)的锁才算成功
  4. 锁的实际有效时间 = 初始有效时间 - 获取锁消耗的时间
  5. 如果获取锁失败,则向所有节点发送释放锁命令

示例代码(使用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分布式锁的关键问题

  1. 原子性:使用Lua脚本确保SETNX和EXPIRE操作的原子性
  2. 锁续期:通过守护线程定期检查并延长锁的有效期
  3. 锁释放:确保只有锁的持有者才能释放锁(value中存储唯一标识)
  4. 集群容错:主从切换可能导致锁丢失,RedLock算法提供了一定解决方案

2.3 基于Zookeeper的实现

实现原理: 利用Zookeeper的临时顺序节点和Watch机制实现分布式锁。

实现步骤

  1. 在指定路径下创建临时顺序节点
  2. 获取父节点下的所有子节点并排序
  3. 判断当前节点是否为序号最小的节点:
    • 是则获取锁成功
    • 否则对前一个节点注册Watcher
  4. 锁释放后,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. 分布式锁的关键特性

一个完善的分布式锁实现应具备以下特性:

  1. 互斥性:同一时刻只有一个客户端能持有锁
  2. 避免死锁:锁必须有超时机制或自动释放机制
  3. 容错性:即使部分节点故障,锁服务仍然可用
  4. 可重入性:同一个客户端可以多次获取同一把锁
  5. 高性能:获取和释放锁的操作要高效
  6. 公平性:获取锁的顺序与请求顺序一致(可选)

4. 分布式锁的最佳实践

  1. 锁粒度的选择

    • 细粒度锁:资源竞争精准,但管理复杂
    • 粗粒度锁:实现简单,但并发度低
  2. 超时设置

    • 获取锁的超时时间:避免长时间等待
    • 锁持有的超时时间:业务操作应在该时间内完成
  3. 锁的释放

    • 必须放在finally块中确保释放
    • 实现锁的可重入性时要正确维护计数器
  4. 异常处理

    • 网络分区时的处理策略
    • 锁服务不可用时的降级方案
  5. 监控与告警

    • 监控锁的获取成功率
    • 监控锁的平均持有时间

5. 常见问题与解决方案

问题1:锁提前过期

  • 现象:业务操作未完成锁已过期
  • 解决方案:实现锁续期机制(watch dog)

问题2:错误释放他人锁

  • 现象:A客户端释放了B客户端的锁
  • 解决方案:锁value中存储唯一标识,释放时验证

问题3:网络分区导致脑裂

  • 现象:多个客户端同时持有锁
  • 解决方案:使用fencing token机制

问题4:锁不可重入

  • 现象:同一线程多次获取锁导致死锁
  • 解决方案:维护持有者线程和计数器

6. 总结与选型建议

  1. 数据库锁:适合并发量低、可靠性要求不高、已有数据库环境的场景
  2. Redis锁:适合高性能、高可用场景,但对一致性要求不能太高
  3. Zookeeper锁:适合强一致性、高可靠场景,但性能相对较低

实际项目中,推荐使用成熟的框架如Redisson或Curator,它们已经处理了各种边界条件和异常情况。对于关键业务,可以考虑结合多种实现方式提高可靠性。

到此这篇关于Java分布式锁详解的文章就介绍到这了,更多相关Java分布式锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 带你快速搞定java并发库

    带你快速搞定java并发库

    本文主要介绍了java高并发写入用户信息到数据库的几种方法,具有很好的参考价值。下面跟着小编一起来看下吧,希望能给你带来帮助
    2021-07-07
  • spring@value注入配置文件值失败的原因分析

    spring@value注入配置文件值失败的原因分析

    这篇文章主要介绍了spring@value注入配置文件值失败的原因分析,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • SpringBoot使用JavaMailSender实现发送邮件+Excel附件

    SpringBoot使用JavaMailSender实现发送邮件+Excel附件

    项目审批完毕后,需要发送邮件通知相关人员,并且要附带数据库表生成的Excel表格,这就要求不光是邮件发送功能,还要临时生成Excel表格做为附件,本文详细介绍了SpringBoot如何使用JavaMailSender实现发送邮件+Excel附件,需要的朋友可以参考下
    2023-10-10
  • 详解Servlet3.0新特性(从注解配置到websocket编程)

    详解Servlet3.0新特性(从注解配置到websocket编程)

    Servlet3.0的出现是servlet史上最大的变革,其中的许多新特性大大的简化了web应用的开发,为广大劳苦的程序员减轻了压力,提高了web开发的效率。
    2017-04-04
  • Java实现数据库图片上传与存储功能

    Java实现数据库图片上传与存储功能

    在现代的Web开发中,上传图片并将其存储在数据库中是常见的需求之一,本文将介绍如何通过Java实现图片上传,存储到数据库的完整过程,希望对大家有所帮助
    2025-03-03
  • Java如何处理延迟任务过程解析

    Java如何处理延迟任务过程解析

    这篇文章主要介绍了Java如何处理延迟任务过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-10-10
  • SpringBoot DBUnit 单元测试(小结)

    SpringBoot DBUnit 单元测试(小结)

    这篇文章主要介绍了SpringBoot DBUnit 单元测试(小结),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-09-09
  • Java如何基于反射机制获取不同的类

    Java如何基于反射机制获取不同的类

    这篇文章主要介绍了Java如何基于反射机制获取不同的类,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-08-08
  • Easycode自动化springboot的curd

    Easycode自动化springboot的curd

    这篇文章主要介绍了Easycode自动化springboot的curd,围绕主题的相关资料展开详细内容,具有一定的参考价值,需要的小伙伴可以参考一下,希望给对你有所帮助
    2022-01-01
  • springboot+mybatis-plus实现自动建表的示例

    springboot+mybatis-plus实现自动建表的示例

    本文主要介绍了springboot+mybatis-plus实现自动建表的示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-06-06

最新评论