深入理解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对象