深入理解Redission释放锁过程

发布于:2025-07-27 ⋅ 阅读:(11) ⋅ 点赞:(0)
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对象


网站公告

今日签到

点亮在社区的每一天
去签到