为了让业务在多线程、分布式环境下不出现超卖、数据错乱、并发安全问题,手写的 Redis 锁代码,存在不可重入、无法重试、锁超时释放三大缺陷。为了解决这些问题,引入了 Redisson 分布式锁。它底层采用 Lua 脚本保证原子性,我们只需要直接调用 API,就能实现安全、可靠、高性能的分布式锁。

一定要看,学习须知

这里学习的 Redisson 分布式锁,只解决了自定义 Redis 锁三大基础问题:可重入、锁重试、锁超时自动续约。但是该锁只连接单个 Redis 节点,没有考虑集群环境。企业级项目中 Redis 一般都是主从集群、多节点部署,因为主从数据同步存在延迟,一旦主节点宕机、从节点上位,就会出现锁丢失、主从一致性安全问题

所以为了解决这个集群多节点的安全漏洞,就需要使用

Redisson 连锁 MultiLock。 (下个笔记讲)

自定义锁的三大问题

  1. 抢锁失败无法重试:当原生锁抢锁失败的时候,流程会直接终止
  2. 锁超时提前释放:业务耗时过长、线程阻塞卡顿,自定义锁会释放,其他线程会乘机抢占锁,引发并发数据安全问题。
  3. 锁不可重入:同一个线程在嵌套业务逻辑下多次加锁,会直接被阻塞拦截,无法适配复杂嵌套逻辑。

为了解决这些问题我们选择 Redisson 分布式锁

一、怎么解决三个问题的原理

1.Redisson 可重入锁底层实现

基础能力 只需要采用 Redis Hash 结构实现

重入锁有啥用: 同一线程嵌套调用多层加锁逻辑时,不会阻塞、不会死锁,适配复杂业务嵌套调用,提升代码健壮性。

为什么 Redisson 可以重入锁:

自定义锁,结构是 string 当加锁第二次的时候会卡死,所以不能重入(嵌套),只有简单 lock:ok 当嵌套的时候会发现上一层已经有锁了,下一层无法嵌套就会卡死。

lock:ok

为什么 Redisson 采用 Hash 不会卡死:

key:锁名称
field:当前线程ID
value:加锁次数

在加锁的时候会,看锁存不存在,是不是自己的线程,如果是就次数加一 然后继续嵌套

2.高性能锁失败重试机制

自定义: 没抢到 → 不停疯狂重试、一直消耗性能

Redisson 高性能重试:没抢到 → 休眠等待别人释放锁发信号 → 才唤醒重试

底层实现:基于 Redis 发布与订阅(Pub/Sub) 机制线程抢锁失败后订阅解锁频道,进入阻塞休眠;当其他线程解锁时,发布消息,唤醒等待线程再重试抢锁。

3.看门狗 WatchDog 自动续约机制

防止业务没跑完、锁提前过期

为什么需要看门狗:

锁默认 30 秒过期, 如果你的业务执行很慢、阻塞、卡顿,超过 30 秒 ,过期了锁会过期释放,其他线程抢锁不安全。

我们必须自动延长锁的过期时间,这就叫做看门狗(想起来有个游戏叫做看门狗,有人玩过吗?)

底层:

  • 线程成功拿到锁
  • Redisson 自动开启一个后台定时任务
  • 默认锁有效期 30s,看门狗每 10s自动续期一次
  • 去 Redis 校验:这把锁是不是当前线程持有的
  • 是自己的锁 → 把锁过期时间重新刷新回 30 秒
  • 无限循环、递归执行,一直续命

二、Redisson 加锁与解锁完整流程 底层实现流程

Redisson 底层全程用 Lua 脚本 保证原子性;

三大核心:Hash 可重入 + 发布订阅重试 + WatchDog 看门狗续约

一、加锁完整流程

执行 Lua 脚本尝试抢锁

情况分支:

① 锁不存在:创建 Hash,记录当前线程 ID,重入次数 = 1

② 锁存在、是当前自己线程:重入次数 +1,刷新过期时间(可重入)

③ 锁存在、是别人线程:抢锁失败,返回锁剩余过期时间 TTL

抢锁成功:

如果没有手动指定锁超时时间(leaseTime=-1)

自动开启 WatchDog 看门狗 定时续约任务

每 10 秒刷新锁为 30 秒有效期,无限续命

抢锁失败:

不会死循环空转(不浪费 CPU)

订阅 Redis 锁释放的 发布订阅消息

进入阻塞等待,限时等待(自己设置的最大等待时间)

收到别人解锁通知 / 等待时间没耗尽 → 再次循环重试抢锁

等待时间耗尽仍没抢到 → 返回加锁失败


二、解锁完整流程

业务执行完毕,调用 unlock (),执行解锁 Lua 脚本

先校验:这把锁是不是当前线程的

不是自己的锁:直接禁止解锁,防止乱删锁

是自己的锁:重入次数 -1

分支判断:

① 重入次数 > 0不删除锁,只刷新过期时间,锁继续生效

② 重入次数 = 0真正删除锁 Key

锁彻底删除后:

通过 publish 发布锁释放消息

唤醒所有正在等待抢锁的其他线程

最后一步:

关闭、取消当前锁绑定的 看门狗定时续约任务

停止自动续期,释放后台线程资源

三、如何实现

1. 引依赖

项目里加入 Redisson 的 starter 依赖

<!-- Redisson 分布式锁依赖 -->
<dependency>
  <groupId>org.redisson</groupId>
  <artifactId>redisson-spring-boot-starter</artifactId>
</dependency>

2. 连接 Redis

配置 Redis 地址、端口,让 Redisson 连上 Redis

3. 注入 RedissonClient 在代码里面写

直接用 @Autowired 拿到客户端

4. 调用 API 加锁、解锁

你可以按照业务需求选

lock () 或 tryLock ()业务写完 unlock ()

代码实现:

无限等待模式(lock.lock ()) 任务不能丢、必须执行、只能来一次的业务

@Autowired  // 注入 RedissonClient 
private RedissonClient redissonClient;

public void doImportantTask() {
    RLock lock = redissonClient.getLock("task:lock");

    try {
        // 一直等,拿不到锁绝不罢休
        lock.lock();

        // 这里写核心业务
        System.out.println("执行必须完成的任务");

    } finally {
        // 必须解锁
        if (lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }
}

限时等待模式(tryLock) 用户请求、秒杀、接口、能重试的普通业务

@Autowired
private RedissonClient redissonClient;

public void createOrder() {
    RLock lock = redissonClient.getLock("order:lock");

    try {
        // 最多等 3 秒,抢不到直接放弃
        boolean locked = lock.tryLock(3, TimeUnit.SECONDS);

        if (!locked) {
            System.out.println("抢锁失败,系统繁忙");
            return;
        }

        // 执行业务(下单、扣库存)
        System.out.println("抢锁成功,开始创建订单");

    } finally {
        if (lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }
}
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐