实战篇 19. 分布式锁 - Redisson 可重入锁原理学习文档

6 阅读3分钟

📚 实战篇 19. 分布式锁 - Redisson 可重入锁原理学习文档

一、 核心痛点:我们手写的 V3 版本为什么“不可重入”?

什么是可重入?

简单来说,就是一个线程拿到锁之后,如果它内部调用的其他方法也需要这把锁,它可以无需等待,直接再次获取这把锁。这就叫“可重入”。Java 原生的 ReentrantLocksynchronized 都是可重入锁。

V3 版本的死穴:

我们之前手写的锁,底层使用的是 Redis 的 String 数据结构(SETNX)。

  • 当线程 A 第一次执行 tryLock() 时,SETNX 成功,写入了 UUID-ThreadA
  • 当线程 A 内部的子方法再次执行 tryLock() 尝试重入时,它会再次去执行 SETNX
  • 因为 Key 已经存在了,SETNX 会直接返回失败!线程 A 就这样被自己加的锁挡在了门外,引发死锁或者业务报错。

二、 破局核心:数据结构的降维打击 (String -> Hash)

为了解决重入问题,Redisson 抛弃了简单的 String 结构,转而使用了 Redis 的 Hash(哈希)结构

在 Redisson 的设计中,一个可重入锁的组成部分如下:

  • KEY(大键): 锁的名称(例如 lock:order)。
  • FIELD(小键): 当前占用这把锁的线程唯一标识(即 UUID + ThreadId)。
  • VALUE(值): 重入的次数(计数器)

通过引入“计数器”,Redisson 完美化解了重入难题。


三、 核心源码剖析:加锁的 Lua 脚本逻辑

当你调用 Redisson 的 lock.tryLock() 时,它的底层实际上是向 Redis 发送了一长串极其严谨的 Lua 脚本。

这段加锁脚本的核心逻辑分为三个分支:

  1. 分支 1:锁压根不存在(没人占用)

    • 判断:exists KEY == 0
    • 动作:使用 hset KEY FIELD 1 创建 Hash 结构,并把计数器设为 1。
    • 动作:使用 pexpire KEY TTL 设置整体的超时时间。
    • 返回:nil(代表加锁成功)。
  2. 分支 2:锁存在,且持有者正是当前线程(触发重入)

    • 判断:hexists KEY FIELD == 1
    • 动作:使用 hincrby KEY FIELD 1 将重入次数加 1(比如从 1 变成 2)。
    • 动作:使用 pexpire KEY TTL 重新刷新这把锁的超时时间。
    • 返回:nil(代表重入成功)。
  3. 分支 3:锁存在,但持有者是别人(加锁失败)

    • 判断:上述条件都不满足。
    • 返回:使用 pttl KEY 返回当前锁还剩多少毫秒过期。外层 Java 代码拿到这个剩余时间后,就会进入等待或重试逻辑。

四、 核心源码剖析:解锁的 Lua 脚本逻辑

“解铃还须系铃人”,既然加锁是通过计数器累加的,那么解锁自然也就是计数器递减的过程。

释放锁的 Lua 脚本逻辑如下:

  1. 分支 1:锁的持有者不是自己(防误删)

    • 判断:hexists KEY FIELD == 0
    • 动作:直接返回 nil,什么都不做。
  2. 分支 2:是自己的锁,执行计数器减 1

    • 动作:使用 hincrby KEY FIELD -1,让重入次数减 1。

    • 子判断 A:如果减完之后,计数器仍然 > 0

      • 说明外层还有方法在使用这把锁,此时绝对不能删除锁
      • 动作:使用 pexpire KEY TTL 重新刷新过期时间。
      • 返回:0。
    • 子判断 B:如果减完之后,计数器 == 0

      • 说明当前线程的所有重入层级都已经彻底执行完毕了。
      • 动作:使用 del KEY 真正从 Redis 中删除这把锁
      • 动作:使用 publish KEY redisson_lock__channel 发送一条广播消息,通知其他正在排队等待的线程:“我释放锁了,你们快来抢!”
      • 返回:1。

五、 学习总结表

对比维度我们手写的 V3 版本Redisson 企业级版本
底层数据结构String 字符串Hash 哈希表
防误删标识存在 Value 里存在 Field 里
重入机制不支持(SETNX 互斥)支持(Value 作为计数器累加)
原子性保障自定义 Lua 脚本内置精细化 Lua 脚本
唤醒等待线程只能靠客户端死循环重试基于 Redis 的 Pub/Sub 发布订阅机制