当前位置: 首页 > news >正文

【分布式锁通关指南 06】源码剖析redisson可重入锁之加锁

引言

在上篇中,我们基于spring boot整合redisson实现了分布式锁,接下来我会带领大家花一些时间来学习redisson如何实现各种锁,所以我们需要先从github上下载它的源码,本篇则先从可重入锁的相关实现开始来为大家做讲解。

加锁流程分析

这里我们按照步骤逐步分析Redisson 可重入锁的加锁流程。

1.首先从入口方法开始 (RLock.lock()):

// RLock 接口的默认实现类 RedissonLock
public void lock() {
    try {
        lock(-1, null, false);
    } catch (InterruptedException e) {
        throw new IllegalStateException();
    }
}

2.核心加锁逻辑实现:

private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
    
    // 获取当前线程ID
    long threadId = Thread.currentThread().getId();
    
    // 尝试获取锁
    Long ttl = tryAcquire(leaseTime, unit, threadId);
    
    // 如果ttl为空,表示获取锁成功
    if (ttl == null) {
        return;
    }
    
    // 如果获取锁失败,订阅到对应的redisson锁channel,等待锁释放消息
    RFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);
    if (interruptibly) {
        subscribeFuture.syncUninterruptibly();
    } else {
        subscribeFuture.sync();
    }

    try {
        while (true) {
            // 再次尝试获取锁
            ttl = tryAcquire(leaseTime, unit, threadId);
            // 成功获取锁,直接返回
            if (ttl == null) {
                break;
            }
            
            // 等待锁释放通知
            if (ttl >= 0) {
                try {
                    await(ttl, TimeUnit.MILLISECONDS);
                } catch (InterruptedException e) {
                    if (interruptibly) {
                        throw e;
                    }
                }
            }
        }
    } finally {
        // 取消订阅
        unsubscribe(subscribeFuture, threadId);
    }
}

3.tryAcquire 方法实现(这里包含了可重入的核心逻辑):

private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) {
    
    // 根据传入的租约时间计算锁的过期时间
    long currentTime = System.currentTimeMillis();
    Long ttl = null;
    
    // 如果指定了租约时间
    if (leaseTime != -1) {
        ttl = tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
    } 
    // 使用默认的过期时间
    else {
        ttl = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), 
            TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
        // 启动看门狗定时续期
        scheduleExpirationRenewal(threadId);
    }
    
    return ttl;
}

梳理一下整个加锁流程:

1. 入口调用:

  • 用户调用 lock() 方法开始加锁
  • 默认使用无限等待时间,且不响应中断

2. 加锁尝试:

  • 首先获取当前线程 ID
  • 调用 tryAcquire 尝试获取锁
  • 如果获取成功(返回 null),则直接返回
  • 如果获取失败,进入等待流程

3. 锁等待流程:

  • 订阅锁释放的 Channel,等待通知
  • 进入循环,不断尝试获取锁
  • 获取成功则退出循环
  • 获取失败则等待指定时间后继续尝试

加锁Lua脚本分析

继续看tryLockInner方法 - 它是最核心的加锁 Lua 脚本:

<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
    // 这里执行 Lua 脚本
    return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
        // 判断锁是否存在
        "if (redis.call('exists', KEYS[1]) == 0) then " +
            // 不存在则创建锁,并设置重入次数为1
            "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
            "redis.call('pexpire', KEYS[1], ARGV[1]); " +
            "return nil; " +
        "end; " +
        // 锁已存在,判断是否是当前线程持有的锁
        "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
            // 是当前线程,则重入次数+1
            "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
            "redis.call('pexpire', KEYS[1], ARGV[1]); " +
            "return nil; " +
        "end; " +
        // 其他线程持有锁,返回锁的过期时间
        "return redis.call('pttl', KEYS[1]);",
        // 这里是参数
        Collections.singletonList(getName()), // KEYS[1] 锁名称
        unit.toMillis(leaseTime), // ARGV[1] 锁过期时间
        getLockName(threadId)); // ARGV[2] 线程标识
}

它的核心逻辑也很简单:首先检查锁是否存在,如果不存在,则直接加锁,且设置重入次数为1;如果存在,先检查是否是当前线程的锁,如果是,则重入次数+1,如果不是,则返回锁的剩余过期时间。

小结

本篇剖析了redisson可重入锁的加锁流程源码,其实这里读者应该可以发现我们前面通过redis手撸的时候的逻辑其实和这里几乎一致,这也是我们学习源码的意义,借鉴别人优秀的设计并为自己所用!


文章转载自:

http://Fgdiozns.bdzps.cn
http://2mPnAaPt.bdzps.cn
http://CRkwfGMd.bdzps.cn
http://1VQkUPSp.bdzps.cn
http://o2Px1TWw.bdzps.cn
http://sgll9oFw.bdzps.cn
http://w1vglIbh.bdzps.cn
http://YUQfkLJA.bdzps.cn
http://U9pjDyjk.bdzps.cn
http://rCXP57Jk.bdzps.cn
http://S7OgLoYj.bdzps.cn
http://NoNvN6RX.bdzps.cn
http://TONgTt7C.bdzps.cn
http://Od4qR7KV.bdzps.cn
http://b0MUaA6d.bdzps.cn
http://DxDmgjRW.bdzps.cn
http://TLxqZrnO.bdzps.cn
http://BcD9D1w1.bdzps.cn
http://5kGQPJyV.bdzps.cn
http://7NakyPdb.bdzps.cn
http://AKLPnnX1.bdzps.cn
http://U6l7DzAp.bdzps.cn
http://uEFZ9QqT.bdzps.cn
http://ntOfcH7K.bdzps.cn
http://xverl2YD.bdzps.cn
http://9JpSAYln.bdzps.cn
http://tjUnv8Hu.bdzps.cn
http://PR7mZhXY.bdzps.cn
http://vXvSimGB.bdzps.cn
http://n7N0u4VM.bdzps.cn
http://www.dtcms.com/a/52622.html

相关文章:

  • 【15】蚂蚁链产品与服务
  • Scala 中的数据类型
  • 上海市闵行区数据局调研云轴科技ZStack,共探数智化转型新路径
  • koa-session设置Cookie后获取不到
  • 解决Spring Boot中LocalDateTime返回前端数据为数组结构的问题
  • 【C#】委托是什么
  • LLM | 论文精读 | CVPR | PEACE : 通过多模态大语言模型(MLLMs)赋能地质图全面理解
  • doris: MySQL
  • ASP .NET Core 学习(.NET9)Serilog日志整合
  • *pu相关概念介绍
  • 获取Kernel32基地址
  • 如何构建一个 Docker 镜像?
  • [数字图像处理]实验三:直方图增强
  • 快速生成viso流程图图片形式
  • web渲染技术与SEO—第一章—SEO详解
  • Redis——快速入门
  • Redis 主从复制、哨兵与集群的关系及工作原理详解
  • 关于 QPalette设置按钮背景未显示出来 的解决方法
  • Spring 为什么要有依赖注入
  • Python快捷手册
  • HCIA-IP路由动态-RIP
  • Qt5 C++ QMap使用总结
  • Unity Shader学习日记 part6 基本光照模型
  • CES Asia 2025增设未来办公教育板块,科技变革再掀高潮
  • Trae 是一款由 AI 驱动的 IDE,让编程更加愉悦和高效。国际版集成了 GPT-4 和 Claude 3.5,国内版集成了DeepSeek-r1
  • 【CXX】4.4 其他构建系统
  • 分布式多卡训练(DDP)踩坑
  • 解锁Android RemoteViews:跨进程UI更新的奥秘
  • 软考架构师笔记-存储管理
  • 边缘计算在豪越智慧消防中的应用探索