ZooKeeper 和 Curator 里头那个可重入锁到底咋实现的,源码深挖分享
- 问答
- 2026-01-02 16:07:31
- 2
锁和ZooKeeper的“临时节点”
你得知道可重入锁是啥意思,简单说,就是同一个线程可以多次获取同一把锁,而不会把自己卡死,你在一个加了锁的方法A里,又调用了另一个也需要同一把锁的方法B,如果是可重入锁,那就没问题;如果不是,线程B在等线程A释放锁,但其实线程A也在等线程B执行完,这就死锁了。
ZooKeeper本身没直接给你一个叫“Lock”的类,它提供的是最基础的“原材料”,主要是临时顺序节点,这东西有两个关键特性:
- 临时性:创建这个节点的客户端(比如你的Java程序)一旦和ZooKeeper服务器的连接断掉,这个节点就会被自动删除,这太有用了,可以避免因为客户端崩溃而导致的锁永远无法释放的问题。
- 顺序性:当你创建节点时,可以指定一个顺序标志,ZooKeeper会在你指定的节点名字后面自动追加一个单调递增的数字序列。
Curator呢,就是一个超级好用的“厨师”,它把这些“原材料”(临时顺序节点)拿来,按照一套成熟的“菜谱”(算法),给你做出了各种口味的“锁”,其中就包括我们今天的主题——可重入锁。
Curator是如何“做”出这把锁的?(源码核心逻辑)
咱们就跟着Curator的源码,看看当你调用acquire()方法时,它背地里都干了啥。
第一步:创建锁节点

当你尝试获取锁时,Curator首先会在ZooKeeper上一个预先商量好的“锁空间”目录下(比如/locks/my_lock),创建一个临时的顺序节点。
假设有三个客户端同时来抢锁,ZooKeeper可能会创建出三个节点:
/locks/my_lock/_c_1
/locks/my_lock/_c_2
/locks/my_lock/_c_3
这里的_c_1、_c_2、_c_3就是ZooKeeper自动生成的顺序号。
第二步:判断自己是不是“第一名”
节点创建好后,Curator会去获取“锁目录”下所有的子节点,并按顺序号从小到大排序,它会看自己创建的那个节点,是不是排在所有子节点里的第一个。
- 如果它是第一个:恭喜你,抢锁成功!Curator会标记当前线程已经获得了锁,并且会记录一些信息(比如重入次数,刚开始是1次),然后
acquire()方法就返回了,你的业务代码可以继续执行了。 - 如果它不是第一个:比如你创建的是
_c_2,前面还有个_c_1,那对不起,你得等着。
第三步:没抢到?那就“监听”前一个节点
这是最精妙的一步,如果自己不是第一名,Curator不会傻傻地不停去问ZooKeeper“轮到我没?”,那样效率太低(这叫“忙等待”),Instead,它会找到排在自己前面的那一个节点(对于_c_2就是_c_1),然后在这个前一个节点上设置一个监听器(Watcher)。
设置完监听器后,当前线程就会被挂起等待。

第四步:等待锁释放和“接班”
持有锁的客户端(也就是_c_1的那个)工作做完了,它会调用release()方法释放锁,释放锁在源码里主要做两件事:
- 删除它自己创建的那个临时节点(也就是
_c_1)。 - 因为
_c_1被删除了,ZooKeeper会通知所有监听了_c_1的客户端。
之前一直在等待的_c_2客户端收到了这个“节点已删除”的通知,它被唤醒后,会重新执行第二步:再去获取一次所有子节点,看看现在自己(_c_2)是不是变成第一个了,如果是,就成功获取锁;如果还不是(这种情况比较罕见,可能是有更早的节点突然出现),那就再监听它新的前一个节点。
这个过程就像排队一样,每个人只关心他前面那个人是不是走了,走了他就往前挪一步。
可重入性是怎么实现的?
上面说的是第一次获取锁,那“可重入”的特性Curator是怎么做到的呢?源码里其实很简单,它没有傻到每次都在ZooKeeper上创建一个新节点。

在Curator框架的源码中(例如InterProcessMutex类),它内部维护了一个ThreadLocal<LockData>的变量。LockData这个对象里主要记录了两样东西:
- 当前持有锁的线程。
- 锁的重入次数。
当你同一个线程再次调用acquire()时,Curator会先检查这个ThreadLocal:
- “哎?当前这个线程的LockData已经存在了?而且锁的路径也对得上?”
- “那好,别折腾ZooKeeper了,直接把重入次数加1(比如从1变成2),然后方法就返回了。”
释放锁的时候(release()),也是先看ThreadLocal里的重入次数:
- “重入次数大于1?(比如是2)”
- “那好,也别删ZooKeeper节点,只是把重入次数减1(从2变1)就完事了。”
- 只有当重入次数减到0的时候,才去执行上面第四步说的,真正删除在ZooKeeper上创建的那个临时节点。
这样一来,同一个线程内多次获取锁,只是在JVM内存里进行简单的计数操作,性能非常高,也完美实现了可重入。
总结一下
Curator的可重入锁,可以理解为一个“基于ZooKeeper临时顺序节点的排队系统 + JVM内存内的重入计数器”。
- 排他性:靠的是ZooKeeper临时顺序节点的唯一性和顺序性,确保只有最小的那个节点能拿到锁。
- 容错性(避免死锁):靠的是临时节点,客户端挂了连接一断,锁自动释放。
- 高效等待:靠的是Watcher机制,避免轮询。
- 可重入性:靠的是JVM线程本地变量(ThreadLocal)来记录重入次数,只在第一次和最后一次与ZooKeeper交互。
挖到底层,它其实就是巧妙地运用了ZooKeeper提供的最简单的几个特性,组合出了一套强大而可靠的分布式锁方案,希望这番源码层面的拆解,能让你对它有更深刻的理解。
本文由称怜于2026-01-02发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:https://haoid.cn/wenda/73174.html
