深入理解Redission释放锁过程
lock.unlock();
调用unlock方法,往下追
@Overridepublic 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里执行的,我们接着往下追
@Overridepublic RFuture unlockAsync(long threadId) { // 创建异步结果对象,用于返回解锁操作最终状态 RPromise result = new RedissonPromise(); // 执行核心解锁操作(发送Lua脚本到Redis) RFuture 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 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获取Entry ExpirationEntry 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对象