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获取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对象