必须分层捕获IllegalMonitorStateException、RedisTimeoutException、RedisConnectionClosedException等具体子类异常,结合isHeldByCurrentThread()校验与remainTimeToLive()监控,在finally中安全解锁并触发分级告警闭环。

分布式锁释放失败不是“可选处理项”,而是必须拦截并响应的关键故障点。它往往意味着锁已失效但业务线程仍误以为持有,或 Redis 连接异常导致解锁指令根本未送达——这会直接引发资源竞争、数据覆盖等严重问题。捕获本身不难,难点在于捕获后能否准确定位原因、区分影响范围,并触发对应动作。
明确要捕获的异常类型
不能只 catch Exception 或 Throwable。Redisson 解锁失败常见于以下具体子类,需分层捕获:
- IllegalMonitorStateException:当前线程并未持有该锁(最常见)。可能因锁已过期、被别人强制删除、或 watchDog 续期失败后自动释放
- RedisTimeoutException:执行 unlock 的 Lua 脚本在 Redis 端超时(如主从同步延迟大、Lua 阻塞)
- RedisConnectionClosedException / NoConnectionAvailableException:客户端连接中断或池耗尽,unlock 请求发不出去
- NullPointerException:lock 对象为 null(初始化失败或提前 close),属代码缺陷,应拦截在调用前
在 finally 中安全捕获并包装异常
释放锁必须放在 try-finally 块内,但 finally 里不能裸调 unlock()。要包裹 try-catch,并将原始异常转化为带上下文的业务异常:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 检查锁是否仍被本线程持有:
lock.isHeldByCurrentThread(),仅在此为 true 时才尝试 unlock - unlock() 调用必须单独 try-catch,避免一个异常导致整个 finally 块中断
- 捕获后构造自定义异常,如
LockReleaseFailureException,包含字段:lockKey、threadId、isStillHeld、causeType - 日志输出必须含 MDC 信息:
traceId、lockKey、host、remainingLeaseMs(可通过lock.remainTimeToLive()获取)
触发分级响应与告警闭环
记录日志只是起点,异常必须驱动实际动作:
立即学习“Java免费学习笔记(深入)”;
- 若
isHeldByCurrentThread() == false,说明锁已丢失,立即上报指标lock_release_mismatch_total{key="xxx"},不告警但计入监控看板 - 若 unlock 抛出连接类异常(如 RedisConnectionClosedException),说明客户端通信层异常,触发中等级别告警(企业微信/钉钉),附 Redis 节点 IP 和最近 3 次连接错误数
- 同一 lockKey 在 1 分钟内连续出现 2 次 unlock 失败,且 cause 是
RedisTimeoutException,判定为该 key 所在 Redis 实例存在性能瓶颈,触发高级别告警并自动标记该 key 为“高风险锁” - 全局异常处理器(如 Spring @ControllerAdvice)中,对未被捕获的 LockReleaseFailureException 补充兜底动作:打印堆栈 + 上报 Prometheus + 主动调用
lock.forceUnlock()(仅限开发/测试环境启用)
避免守护线程干扰释放逻辑
绝不把 unlock 放在 Daemon Thread 中执行。守护线程被 JVM 强制终止时,finally 不执行,会导致“已捕获异常却未真正释放”的假象。正确做法是:
- 所有 unlock 必须由业务主线程或明确生命周期可控的线程(如 Spring @Async 管理的线程池)执行
- 禁用自定义看门狗线程;完全依赖 Redisson 内置 WatchDog —— 它运行在 Netty EventLoop 中,稳定性远高于用户手写守护线程
- 加锁时显式指定 leaseTime(如
tryLock(3, 60, TimeUnit.SECONDS)),确保即使 WatchDog 失效,锁也会在 60 秒后自动释放

















