【分布式锁通关指南 10】源码剖析redisson之MultiLock的实现
引言
本期我们将把目光聚焦在 Redisson 中另一个颇具代表性的分布式锁实现——MultiLock。它的核心思想是:一次性对多个独立的 RLock 进行加锁或解锁操作,只有当多个锁都成功加锁时才算真正完成锁的获取,一旦有任何一个失败,整体操作都会回滚。这种“整锁整放”的方式,能更好地满足某些高要求的分布式业务场景。
介绍
在分布式环境中,如果我们将数据拆分到不同的 Redis 实例、集群或是不同的 key 上,有时会遇到需要“一次性对 N 个资源都上锁,才算占用资源”的场景。使用 Redisson 的 MultiLock
可以极大地简化这类需求的实现。它提供了一个整合多个 RLock
的抽象,对外暴露成单一的锁接口,使用起来就像在操作一把锁,而内部却是对多把锁的组合操作。
它的典型应用场景包括:
- 同时锁定多个不同 Redis key,保证“要么全加锁成功,要么全部不加锁”。
- 跨多个机房 / Redis 节点时的互斥需求,尤其在做异步、分布式任务调度时,减少了操作多个锁的繁琐。
接下来让我们直击源码,看一下 RedissonMultiLock
(简称 MultiLock)是如何实现这一套逻辑的。
加锁
在 Redisson 源码里,MultiLock
的主要实现类是 org.redisson.RedissonMultiLock
。其核心属性是一个 List<RLock>
,用来保存所有需要一起加锁的锁。它本质上也是一个 java.util.concurrent.locks.Lock
,所以有类似的 lock()
和 tryLock()
等方法。
下面截取关键的加锁逻辑(为方便说明,做了适当精简):
public class RedissonMultiLock implements Lock {private final List<RLock> locks = new ArrayList<>();public RedissonMultiLock(RLock... locks) {this.locks.addAll(Arrays.asList(locks));}@Overridepublic void lock() {lock(-1, null);}@Overridepublic void lock(long leaseTime, TimeUnit unit) {boolean locked = tryLock(leaseTime, unit);if (!locked) {throw new IllegalStateException("Unable to acquire lock");}}@Overridepublic boolean tryLock(long time, TimeUnit unit) throws InterruptedException {long startTime = System.currentTimeMillis();List<RLock> acquiredLocks = new ArrayList<>();for (RLock lock : locks) {// 计算剩余的等待时间long elapsed = System.currentTimeMillis() - startTime;long remain = time - elapsed;if (remain <= 0 && time != -1) {// 超时了,回滚所有已获取的锁unlockInner(acquiredLocks);return false;}// 尝试获取锁boolean success;if (time == -1) {success = lock.tryLock(); // 不限等待时间} else {success = lock.tryLock(remain, unit);}if (!success) {// 获取锁失败,回滚unlockInner(acquiredLocks);return false;}acquiredLocks.add(lock);}return true;}private void unlockInner(List<RLock> locks) {for (RLock lock : locks) {try {lock.unlock();} catch (Exception e) {// 异常处理,通常记录日志或忽略}}}// ...
}
从这段代码中,我们可以看到:
- MultiLock 会将“要一起加锁”的多个
RLock
封装进一个List
; - 当调用
tryLock
时,会挨个尝试获取每个RLock
; - 若所有锁都获取成功,才返回
true
; - 如果中途有任何一个锁获取失败或者超时,就会调用
unlockInner
方法,依次释放已成功获取的锁,回滚到初始状态,保持分布式环境下的原子性。
这样就保障了“要么所有锁都成功加锁,要么一个都不会留存”,消除了状态不一致的风险。
释放锁
MultiLock 的释放逻辑同样是“全部释放”或“都不释放”。来看下相关核心方法 unlock()
的实现(截取简化版本):
public void unlock() {// 这里我们是一次性 unlock 所有 locksfor (RLock lock : locks) {try {lock.unlock();} catch (Exception e) {// 可能出现解锁异常,比如锁不属于当前线程等情况,做必要处理}}
}
可以看到,unlock()
内部直接遍历所有 RLock
并执行解锁操作。这意味着无论中途某个锁因为“非本线程占用”等原因导致报错,其余锁也会继续解锁,力求释放尽可能多的锁,尽量避免“部分锁未释放”造成死锁风险。
以此也体现了 MultiLock 的一贯思路:要么全部锁住,要么全部释放,让多个分布式锁在逻辑上“捆绑”成一体。
如何保证“一致性”?
- 获取失败即回滚
当调用tryLock
时,如果期间任何一个分布式锁无法加锁成功,就立即回滚(释放已获取的锁)。这是确保多锁原子性的关键。 - 重复可重入语义仍需依赖具体 RLock
如果多个RLock
中有些是可重入锁,那么在同一线程下反复获取时,并不会阻塞。MultiLock 并不会额外重写可重入逻辑,它更多地是一个“协调器”,背后依然由各个RLock
自身的 reentrant 实现来支撑。 - 统一的超时控制
tryLock(long time, TimeUnit unit)
会逐一减少剩余可用时间,避免因为某个锁获取太慢导致整个流程卡死。 - 释放过程对每个锁都负责
哪怕出现解锁异常,MultiLock 也会继续释放其他锁,将风险与影响降至最低。
小结
RedissonMultiLock
通过将多把 RLock
打包成一个“组合锁”,让使用者在编程时只需关心“我拿到所有锁了吗?所有锁都释放了吗?”。它背后通过遍历加锁并回滚的策略,保证了原子性,避免了分布式环境下常见的“锁定不一致”问题。
与之前的公平锁等其他锁实现相比,MultiLock 并不是通过 Lua 脚本在单个 Redis 实例上实现的,而是通过对多个锁对象的封装来保证“一起成功或一起失败”。它更多用于满足“一次性锁定多资源”的场景,这比单一锁更适用分布式业务中对一致性、原子性要求更高的场景。
希望本文能帮助大家厘清 MultiLock 的实现原理。与其余 Redisson 锁一样,阅读源码的过程能让我们更好地理解其在分布式场景下如何保证安全与高效,也能启发我们在设计自定义分布式组件时,如何通过“组合”思维来化繁为简。我们下一期再见!
相关文章:
【分布式锁通关指南 10】源码剖析redisson之MultiLock的实现
引言 本期我们将把目光聚焦在 Redisson 中另一个颇具代表性的分布式锁实现——MultiLock。它的核心思想是:一次性对多个独立的 RLock 进行加锁或解锁操作,只有当多个锁都成功加锁时才算真正完成锁的获取,一旦有任何一个失败,整体操…...

DBF Converter:高效转换DBF文件,满足多样化数据处理需求
DBF Converter 是一款功能强大的数据转换工具,专为需要将DBF文件转换为其他格式的用户设计。它支持将DBF文件转换为CSV、Excel、HTML、SQL等多种常见格式,满足用户在不同场景下的数据处理需求。无论是数据迁移、报表生成还是日常数据处理,DBF…...
Java—— 方法引用 : :
方法引用是什么 把已经存在的方法拿过来用,当做函数式接口中抽象方法的方法体 方法引用符 :: 方法引用的条件 1.需要有函数式接口 2.被引用方法必须已经存在 3.被引用方法的形参和返回值需要跟抽象方法保持一致 4.被引用方法的功能要满足当前…...

Jmeter 安装包与界面汉化
Jmeter 安装包: 通过网盘分享的文件:CSDN-apache-jmeter-5.5 链接: https://pan.baidu.com/s/17gK98NxS19oKmkdRhGepBA?pwd1234 提取码: 1234 Jmeter界面汉化:...
6 任务路由与负载均衡
一、任务路由核心机制 1.1 静态路由配置 # celeryconfig.pytask_routes {# 精确匹配任务路径payment.process_order: {queue: priority_payment},# 通配符匹配任务类型report.*: {queue: low_priority_reports},# 正则表达式匹配re.compile(r^video\.(encode|compress)): {q…...

【C++】 —— 笔试刷题day_29
一、排序子序列 题目解析 一个数组的连续子序列,如果这个子序列是非递增或者非递减的;这个连续的子序列就是排序子序列。 现在给定一个数组,然后然我们判断这个子序列可以划分成多少个排序子序列。 例如:1 2 3 2 2 1 可以划分成 …...
Ruby 循环与迭代器
Ruby 循环与迭代器 循环迭代器timesuptostep 循环 。。。。 迭代器 迭代器本质上可以理解为是循环的一种类型 times 3.times do print "Ho! " end begin Ho! Ho! Ho! end上述代码表示我们对当前 block 部分中的内容循环三次。最终,我们打印出了三个…...
力扣-39.组合总和
题目描述 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。 candidates 中的 同一个 数字可以 无限制重复被…...
优化 Element UI 表格样式,隐藏滚动条但保持滚动功能
优化 Element UI 表格样式,隐藏滚动条但保持滚动功能 前言 在基于 Element UI 的项目中,el-table 是非常常用的表格组件。默认情况下,表格的滚动条可能影响页面的美观,特别是在视觉设计上希望更简洁时。本文分享一段优化的 CSS …...
线程池(ThreadPoolExecutor)实现原理和源码细节是Java高并发面试和实战开发的重点
一、线程池核心流程图 ----------------- | 提交任务 | submit/execute -----------------|v ----------------- | 判断核心线程数 | < corePoolSize? -----------------|Yes |Nov v [创建新线程] -----------------| 队列是否满&a…...

MongoTemplate 基础使用帮助手册
前言 MongoDB 是一种流行的 NoSQL 数据库,适合存储大量的非结构化数据。MongoTemplate 是 Spring Data MongoDB 中的一个核心组件,它提供了一组丰富的 API 来与 MongoDB 进行交互。它封装了许多常见的数据库操作,使开发者能够轻松执行 CRUD 操…...

图像处理:预览并绘制图像细节
前言 因为最近在搞毕业论文的事情,要做出一下图像细节对比图,所以我这里写了两个脚本,一个用于框选并同时预览图像放大细节,可显示并返回框选图像的坐标,另外一个是输入框选图像的坐标并将放大的细节放置在图像中&…...

力扣热题——最长相邻不相等子序列 |
题目要求从字符串数组 words 中选出一个最长的子序列,使得该子序列中相邻字符串对应的 groups 数组中的值不同。通过贪心算法,可以高效地解决该问题。具体步骤为:初始化一个结果列表,遍历 words 数组,检查当前字符串的…...
【抽丝剥茧知识讲解】引入mybtis-plus后,mapper实现方式
目录 前言一、传统 Mapper 接口方式二、继承 BaseMapper 的方式三、自定义通用 Mapper 的方式四、使用 MyBatis-Plus 的 ActiveRecord 模式五、使用 MyBatis-Plus 的 IService 接口六、使用建议 前言 mapper文件,作为Mybatis框架中定义SQL语句和映射关系的配置文件&…...

ssti刷刷刷
[NewStarCTF 公开赛赛道]BabySSTI_One 测试发现过滤关键字,但是特殊符号中括号、双引号、点都能用 可以考虑拼接或者编码,这里使用拼接 ?name{{()["__cla"~"ss__"]}}?name{{()["__cla"~"ss__"]["__ba&…...

java+selenum专题(一)
环境搭建部署篇-> 1.简介 java版的selenium,介绍一下java selenium自动化测试。大致和pythonselenium自动化测试差不多。基于java和selenium做自动化测试,因此你必须会搭建基本的开发环境,掌握python基本的语法和一个IDE来进行开发&…...
物体雅克比、空间雅克比、解析雅克比、几何雅克比
在机器人学中,雅可比矩阵是连接广义坐标速度与末端执行器速度的关键工具。根据应用场景和参考系的不同,雅可比矩阵可分为物体雅可比(Body Jacobian)、空间雅可比(Space Jacobian)、解析雅可比(A…...

[逆向工程]DebugView捕获WPS日志?解析未运行WPS时Shell扩展加载的原因与解决方案(二十五)
[逆向工程]DebugView捕获WPS日志?解析未运行WPS时Shell扩展加载的原因与解决方案(二十五) 引言:一个“幽灵”般的日志问题 你是否在使用 DebugView 排查系统问题时,发现日志中频繁出现 WPS 相关模块(如 k…...

ACM模式用Scanner和System.out超时的解决方案和原理
Hi~!这里是奋斗的明志,很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~~ 🌱🌱个人主页:奋斗的明志 🌱🌱所属专栏:笔试强训 📚本系列文章为个人学…...

Java注解详解:从入门到实战应用篇
1. 引言 Java注解(Annotation)是JDK 5.0引入的一种元数据机制,用于为代码提供附加信息。它广泛应用于框架开发、代码生成、编译检查等领域。本文将从基础到实战,全面解析Java注解的核心概念和使用场景。 2. 注解基础概念 2.1 什…...

QML 属性动画、行为动画与预定义动画
目录 引言相关阅读本文使用的动画属性工程结构示例解析示例1:属性动画应用示例2:行为动画实现示例3:预定义动画 总结工程下载 引言 QML动画系统为界面元素提供了流畅的过渡效果。本文通过三个示例,结合属性动画(PropertyAnimatio…...

window nvidia-smi命令 Failed to initialize NVML: Unknown Error
如果驱动目录下的可以执行,那可能版本原因 "C:\Program Files\NVIDIA Corporation\NVSMI\nvidia-smi"复制"C:\Program Files\NVIDIA Corporation\NVSMI\nvidia-smi.exe"替换 C:\Windows\System32\nvidia-smi.exe 或者 把C:\Windows\System3…...

自学嵌入式 day19-数据结构 链表
二、线性表的链式存储 1.特点: (1)线性表链式存储结构的特点是一组任意的存储单位存储线性表的数据元素,存储单元可以是连续的,也可以不连续。可以被存储在任意内存未被占用的位置上。 (2)所以…...

东芝第3代SiC MOSFET助于降低应用中电源损耗
功率器件是管理和降低各种电子设备电能功耗以及实现碳中和社会的重要元器件。由于与比硅材料相比,碳化硅具有更高的电压和更低的损耗,因此碳化硅(SiC)被广泛视为下一代功率器件的材料。虽然碳化硅功率器件目前主要用于列车逆变器&…...
Vue 2.0学习
个人简介 👨💻个人主页: 魔术师 📖学习方向: 主攻前端方向,正逐渐往全栈发展 🚴个人状态: 研发工程师,现效力于政务服务网事业 🇨🇳人生格言&…...
Mendix 中的XPath 令牌(XPath Tokens)详解
在 Mendix 中,XPath 令牌(XPath Tokens) 是一种特殊的动态参数化查询技术,允许你在 XPath 表达式中使用变量或上下文相关的值,从而实现更灵活的查询逻辑。 1. 什么是 XPath 令牌? XPath 令牌是 Mendix 提…...
Spring Batch学习,和Spring Cloud Stream区别
Spring Batch学习,和Spring Cloud Stream区别 1. 使用Spring Initializr创建项目2. 使用步骤构建作业(Chunk 模式)🧩 场景说明🧰 1. 示例目录结构📄 2. 创建输入文件(users.csv)&…...
【技术原理】Linux 文件时间属性详解:Access、Modify、Change 的区别与联系
在 Linux 系统中,每个文件都有三个核心时间属性:Access Time (atime)、Modify Time (mtime) 和 Change Time (ctime)。它们分别记录文件不同维度的变更信息,以下是具体区别与联系: 一、定义与触发条件 时间属性定义触发条件示例A…...
k8s之LoadBalancer Service 解析
Kubernetes LoadBalancer Service 解析:IP与端口详解 服务类型与IP解析 Service 是 Kubernetes 中的资源类型,用来将一组 Pod 的应用作为网络服务公开。每个 Pod 都有自己的 IP,但是这个 IP 的生命周期与 Pod 生命周期一致,也就…...
Vue3项目使用ElDrawer后select方法不生效
Vue3 项目中 ElDrawer 内 ElSelect 下拉框 z-index 失效问题分析与解决方案 问题描述问题分析解决方案结论 问题描述 在 Vue3 项目中使用 Element Plus 的 ElDrawer 组件时,当在抽屉内部使用 ElSelect 组件,发现下拉选择框(dropdownÿ…...