当前位置: 首页 > news >正文

深入理解Redission释放锁过程

lock.unlock();

调用unlock方法,往下追

@Override
public void unlock() {try {// 1. 执行异步解锁操作并同步等待结果// - 获取当前线程ID作为锁持有者标识// - unlockAsync()触发Lua脚本执行实际解锁// - get()方法阻塞直到异步操作完成get(unlockAsync(Thread.currentThread().getId()));} catch (RedisException e) {// 2. 异常处理:识别非法解锁场景if (e.getCause() instanceof IllegalMonitorStateException) {// 2.1 特殊处理:当前线程非锁持有者// - 通常在Lua脚本返回nil时触发// - 表示尝试释放未被当前线程持有的锁throw (IllegalMonitorStateException) e.getCause();} else {// 2.2 其他Redis异常(如连接问题)// - 网络中断、Redis宕机等场景throw e;}}// 3. 成功执行路径://   - Lua脚本返回0(重入锁部分释放)或1(完全释放)//   - 后台自动触发看门狗任务取消(完全释放时)
}

再往下追unlcokAsync这个异步方法
这里调用解锁方法unlockInnerAsync同样返回了RFutrue,当lua脚本执行完过后,RFutrue就会变成完成状态,回调用回调函数onComplete,lua脚本就是再unlockInnerAsync里执行的,我们接着往下追

@Override
public RFuture<Void> unlockAsync(long threadId) {// 创建异步结果对象,用于返回解锁操作最终状态RPromise<Void> result = new RedissonPromise<Void>();// 执行核心解锁操作(发送Lua脚本到Redis)RFuture<Boolean> future = unlockInnerAsync(threadId);// 注册回调函数处理解锁结果future.onComplete((opStatus, e) -> {// 关键步骤:无论解锁成功与否,都取消看门狗续期任务// 防止锁释放后继续续期(相当于"喂狗"操作停止)cancelExpirationRenewal(threadId);// 异常处理:Redis操作出错if (e != null) {result.tryFailure(e);  // 设置结果为失败并传递异常return;}// 非法状态检查:opStatus为null表示解锁失败// 常见原因:尝试释放非当前线程持有的锁if (opStatus == null) {// 构造详细的非法状态异常信息IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: "+ id + " thread-id: " + threadId);result.tryFailure(cause);  // 设置结果为失败return;}// 解锁成功:设置结果为成功result.trySuccess(null);});// 返回异步结果对象return result;
}
protected RFuture<Boolean> unlockInnerAsync(long threadId) {return evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,-- 1. 验证锁持有者身份
if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then return nil;  -- 非持有者尝试解锁
end; -- 2. 减少重入计数
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); -- 3. 判断是否完全释放
if (counter > 0) then -- 3.1 未完全释放(重入场景)redis.call('pexpire', KEYS[1], ARGV[2]);  -- 更新过期时间return 0;  -- 返回未完全释放标识
else -- 3.2 完全释放redis.call('del', KEYS[1]);  -- 删除锁redis.call('publish', KEYS[2], ARGV[1]);  -- 发布解锁消息return 1;  -- 返回成功释放标识
end; return nil;  -- 默认返回(不会执行到这里)Arrays.asList(getName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId));}

 再执行完过后回取消定时任务,我们追进去,这里设计到全局静态map EXPIRATION_RENEWAL_MAP 放一张流程图方便回忆这个map

void cancelExpirationRenewal(Long threadId) {// 从全局MAP获取EntryExpirationEntry entry = EXPIRATION_RENEWAL_MAP.get(getEntryName());if (entry != null) {// 关键操作:移除线程记录entry.removeThreadId(threadId);// 检查是否完全释放if (entry.hasNoThreads()) {// 取消定时任务Timeout timeout = entry.getTimeout();if (timeout != null) {timeout.cancel();}// 从全局MAP移除EXPIRATION_RENEWAL_MAP.remove(getEntryName());}}
}

可以看到map存储的是锁的名称和entry对象 entry对象里面放入了线程id,所以释放的时候先从entry移除线程id,如果没有了线程id再从map里移除entry对象

http://www.dtcms.com/a/299813.html

相关文章:

  • Blender入门笔记(一)
  • 利用RAII与析构函数避免C++资源泄漏
  • 基于DataX的数据同步实战
  • 中电建路桥集团有限公司重大项目管理办公室成立
  • 【安全漏洞】网络守门员:深入理解与应用iptables,守护Linux服务器安全
  • Linux 如何统计系统上各个用户登录(或者登出)记录出现的次数?
  • Ubuntu安装node-red
  • 磁悬浮轴承转子不平衡质量控制策略设计:原理、分析与智能实现
  • C/C++中常量放置在比较操作符左侧
  • 基于匿名管道的多进程任务池实现与FD泄漏解决方案
  • 消息缓存系统
  • Docker学习日志-Docker容器配置、Nginx 配置与文件映射
  • Vim 进阶教程
  • React入门学习——指北指南(第四节)
  • Spring核心:Bean生命周期、外部化配置与组件扫描深度解析
  • 龙迅#LT8711UXD适用于Type-C/DP1.4 /EDP转 HDMI2.0 功能,分辨率高达4K60HZ,可支持HDCP!
  • 01 01 02 第一部分 C++编程知识 C++入门 第一个C++程序
  • 其他世界的自来水
  • 【图像理解进阶】如何在自己的数据集上释放segment anything模型方案的潜力?
  • 20250726-3-Kubernetes 网络-Service三种常用类型_笔记
  • 服务器被网络攻击后该如何进行处理?
  • “累”这一身体某部位的感觉可能较多来源于对局部血流阻力的感知
  • 教育培训系统源码解析:如何打造高可扩展的在线学习平台?
  • 疏老师-python训练营-Day26函数专题1:函数定义与参数
  • Dify开发教程笔记(一): 文件及系统参数变量说明及使用
  • 【n8n教程笔记——工作流Workflow】文本课程(第一阶段)——5.3 过滤订单 (Filtering orders)
  • 【Canvas与壁纸】蓝底白四方块花电脑壁纸1920x1080
  • 大模型——字节Coze重磅开源!Dify何去何从
  • Web后端进阶:springboot原理(面试多问)
  • Android Fragment 全解析