Redisson可重入锁原理-举个通俗的栗子

发布于:2025-09-14 ⋅ 阅读:(12) ⋅ 点赞:(0)

我们用“家门钥匙”和“卧室门钥匙”的比喻来彻底讲明白。

故事:你的家和你的卧室

想象一下,你有一个家(代表一个共享资源,比如一个方法或一段代码)。

  1. 第一道锁:家门锁
    为了保护你的家,你有一把家门钥匙这就是第一把分布式锁)。你拿到这把钥匙,才能进入家门。

  2. 第二道锁:卧室门锁
    你的卧室里放着最贵重的东西。所以卧室门上还有一把锁,需要卧室门钥匙才能打开。


场景一:不可重入锁的尴尬(问题所在)

现在你想进卧室拿东西。你的动线是:先进家门 -> 再进卧室

  1. 你成功用家门钥匙打开了家门锁,你现在站在了家里。
  2. 你走到卧室门口,准备用卧室门钥匙开门。
  3. 突然,一个保安跳出来说:“对不起,先生!要拿卧室钥匙,你必须先证明你家门钥匙是有效的。”
  4. 你说:“我这不是已经在家里了吗?这还不能证明?”
  5. 保安说:“不行!我们的规则是,要申请任何一把钥匙,都必须从家门外申请。请你现在先退出家门,把家门钥匙还给我,然后再重新向我申请家门钥匙。如果申请成功,你才能再申请卧室钥匙。”

你肯定会疯掉! 这就是不可重入锁的问题:同一个线程,已经持有外层的锁了,但在申请内层的锁时,会被要求先释放外层的锁,从而导致死锁一样的尴尬局面。


场景二:Redisson可重入锁(解决方案)

现在,Redisson可重入锁登场了。它就像一个非常智能的物业管家

  1. 你还是想进卧室拿东西。
  2. 你向管家申请:“我要进家门。” 管家看了看记录本,发现目前没人进家,于是把家门钥匙给了你,并在本子上记下:“业主张三,持有家门钥匙 1 把”。你顺利进门。
  3. 你走到卧室门口,又向管家申请:“我要进卧室。”
  4. 这时,聪明的管家不会死板地让你先退出家门。他会做一件事:他查了一下他的记录本
  5. 记录本上清楚地写着:“当前申请卧室钥匙的人,正是已经持有1把家门钥匙的张三!”
  6. 管家心想:“哦,是张三自己啊,他已经在家里了,只是想进里面的房间,很合理。”
  7. 于是,管家没有收回你的家门钥匙,而是直接把卧室钥匙也给了你。同时,他在本子上把记录更新为:“业主张三,持有家门钥匙 1 把,卧室钥匙 1 把”

这个“记录本”,就是Redisson实现可重入锁的核心!


Redisson的实现原理(管家的记录本)

在技术层面,Redisson在Redis里存储锁的数据结构不是一个简单的 “lock_name”: “1”,而是类似这样一个Hash结构:

Key(锁名) Field(字段) Value(值)
“my_lock” “8743c9c0-0795-49...” (UUID+线程ID) 2 (锁的重入次数)

这个结构是什么意思呢?

  • Key (my_lock): 就是锁的名字,比如“家门锁”。
  • Field (UUID+线程ID): 这就是管家的记录本上你的名字(唯一标识你是谁)。UUID代表你的JVM进程,线程ID代表是你这个进程下的哪个线程。
  • Value (2): 这代表你在这个锁上重入了多少次。比如,你进了家门(重入次数+1),又进了卧室(重入次数再+1),所以当前重入次数是2。

整个流程如何工作:

  1. 第一次加锁(进家门):

    • 线程A来加锁。Redis里没有这个锁的记录。
    • Redisson执行Lua脚本,在Redis里创建Hash结构:my_lock: { “thread_A_id”: 1 }
    • 加锁成功!
  2. 第二次重入加锁(进卧室):

    • 同一个线程A再次来加同一把锁
    • Redisson的Lua脚本会检查:my_lock这个Key的Field里面,有没有thread_A_id这个字段?
    • 发现有了! 说明是“自己人”。
    • 脚本不会拒绝,而是将对应的Value值从 1 增加2my_lock: { “thread_A_id”: 2 }
    • 加锁成功!线程A继续执行。
  3. 释放锁(出卧室/出家门):

    • 每次线程A释放锁时,Redisson的Lua脚本会把重入次数减1
    • 比如从 2 减到 1,这代表你只是走出了卧室,但还在家里。
    • 只有当重入次数减到0时,脚本才会真正地删除 my_lock 这个Key,表示你完全离开了家,把锁彻底释放了。

总结

Redisson可重入锁的原理就是:

  1. 看人下菜碟:通过UUID+线程ID来标识加锁者的身份。
  2. 计数机制:在Redis中用一个数字(重入次数)来记录同一个线程重复加锁了多少次。
  3. 安全的原子操作:所有的判断和计数增减,都通过Lua脚本原子完成,保证线程安全。

这样一来,同一个线程就可以多次获取同一把锁而不会把自己阻塞死,完美解决了嵌套调用的问题。就像你有了一把家门钥匙,就可以自由进出所有房间,而不用反复地出门、进门。