当前位置:

首页 > 编程开发 > springboot基于redisson如何实现redis分布式可重入锁源码解析

springboot基于redisson如何实现redis分布式可重入锁源码解析

Redis Assistant
Redis Assistant

Redis Assistant 是一款为Redis设计的高效可视化管理与监控工具,提供多种操作模式,高效处理服务器数据。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

立即下载
¥19
Windows/macOS/Linux 2026-08-22
正版软件 Windows macOS软件 Linux 数据库管理工具

一、前言我们在实现使用Redis实现分布式锁,最开始一般使用SETresource-nameanystringNXEXmax-lock-time进行加锁,使用Lua脚本保证原子性进行实现释放锁。这样手动实现比较麻烦,对此Redis官网也明确说Java版使用Redisson来实现。小编也是看了官网慢慢的摸索清楚,特写此记录一下。从官网到整合Springboot到源码解读,以单节点为例。二、为什么使用Redisson1.我们打开官网redis中文官网2.我们可以看到官方让我们去使用其他3.打开官方推荐4.找到

本文目录

        一、前言

        我们在实现使用Redis实现分布式锁,最开始一般使用SET resource-name anystring NX EX max-lock-time进行加锁,使用Lua脚本保证原子性进行实现释放锁。这样手动实现比较麻烦,对此Redis官网也明确说Java版使用Redisson来实现。小编也是看了官网慢慢的摸索清楚,特写此记录一下。从官网到整合Springboot到源码解读,以单节点为例。

        二、为什么使用Redisson

        1. 我们打开官网

        redis中文官网

        2. 我们可以看到官方让我们去使用其他

        springboot基于redisson如何实现redis分布式可重入锁源码解析

        3. 打开官方推荐

        springboot基于redisson如何实现redis分布式可重入锁源码解析

        4. 找到文档

        Redisson地址

        springboot基于redisson如何实现redis分布式可重入锁源码解析

        5. Redisson结构

        springboot基于redisson如何实现redis分布式可重入锁源码解析

        三、Springboot整合Redisson

        1. 导入依赖

        
            org.springframework.boot
            spring-boot-starter-data-redis
        
        
            redis.clients
            jedis
        
        
        
            org.redisson
            redisson
            3.12.0
        

        2. 以官网为例查看如何配置

        springboot基于redisson如何实现redis分布式可重入锁源码解析

        3. 编写配置类

        import org.redisson.Redisson;
        import org.redisson.api.RedissonClient;
        import org.redisson.config.Config;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;
        
        /**
         * @author wangzhenjun
         * @date 2022/2/9 9:57
         */
        @Configuration
        public class MyRedissonConfig {
        
            /**
             * 所有对redisson的使用都是通过RedissonClient来操作的
             * @return
             */
            @Bean(destroyMethod="shutdown")
            public RedissonClient redisson(){
                // 1. 创建配置
                Config config = new Config();
                // 一定要加redis://
                config.useSingleServer().setAddress("redis://192.168.17.130:6379");
                // 2. 根据config创建出redissonClient实例
                RedissonClient redissonClient = Redisson.create(config);
                return redissonClient;
            }
        }

        4. 官网测试加锁例子

        springboot基于redisson如何实现redis分布式可重入锁源码解析

        5. 根据官网简单Controller接口编写

        @ResponseBody
        @GetMapping("/hello")
        public String hello(){
            // 1.获取一把锁,只要锁名字一样,就是同一把锁
            RLock lock = redisson.getLock("my-lock");
            // 2. 加锁
            lock.lock();// 阻塞试等待  默认加的都是30s
            // 带参数情况
            // lock.lock(10, TimeUnit.SECONDS);// 10s自动解锁,自动解锁时间一定要大于业务的执行时间。
            try {
                System.out.println("加锁成功" + Thread.currentThread().getId());
                Thread.sleep(30000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // 3. 解锁
                System.out.println("解锁成功:" + Thread.currentThread().getId());
                lock.unlock();
            }
            return "hello";
        }

        6. 测试

        springboot基于redisson如何实现redis分布式可重入锁源码解析

        四、lock.lock()源码分析

        1. 打开RedissonLock实现类

        springboot基于redisson如何实现redis分布式可重入锁源码解析

        2. 找到实现方法

        @Override
        public void lock() {
            try {
            	// 我们发现不穿过期时间源码默认过期时间为-1
                lock(-1, null, false);
            } catch (InterruptedException e) {
                throw new IllegalStateException();
            }
        }

        3. 按住Ctrl进去lock方法

        private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
        	// 获取线程的id,占有锁的时候field的值为UUID:线程号id
            long threadId = Thread.currentThread().getId();
            // 尝试获得锁
            Long ttl = tryAcquire(leaseTime, unit, threadId);
            // lock acquired 获得锁,返回
            if (ttl == null) {
                return;
            }
        	// 这里说明获取锁失败,就通过线程id订阅这个锁
            RFuture future = subscribe(threadId);
            if (interruptibly) {
                commandExecutor.syncSubscriptionInterrupted(future);
            } else {
                commandExecutor.syncSubscription(future);
            }
        
            try {
            	// 这里进行自旋,不断尝试获取锁
                while (true) {
                	// 继续尝试获取锁
                    ttl = tryAcquire(leaseTime, unit, threadId);
                    // lock acquired 获取成功
                    if (ttl == null) {
                    	// 直接返回,挑出自旋
                        break;
                    }
        
                    // waiting for message 继续等待获得锁
                    if (ttl >= 0) {
                        try {
                            future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                        } catch (InterruptedException e) {
                            if (interruptibly) {
                                throw e;
                            }
                            future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                        }
                    } else {
                        if (interruptibly) {
                            future.getNow().getLatch().acquire();
                        } else {
                            future.getNow().getLatch().acquireUninterruptibly();
                        }
                    }
                }
            } finally {
             	// 取消订阅
                unsubscribe(future, threadId);
            }
        //        get(lockAsync(leaseTime, unit));
        }

        4. 进去尝试获取锁方法

        springboot基于redisson如何实现redis分布式可重入锁源码解析

        private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) {
        	// 直接进入异步方法
            return get(tryAcquireAsync(leaseTime, unit, threadId));
        }
        
        private  RFuture tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {
            // 这里进行判断如果没有设置参数leaseTime = -1
            if (leaseTime != -1) {
                return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
            }
            // 此方法进行获得锁,过期时间为看门狗的默认时间
            // private long lockWatchdogTimeout = 30 * 1000;看门狗默认过期时间为30s
            // 加锁和过期时间要保证原子性,这个方法后面肯定调用执行了Lua脚本,我们下面在看
            RFuture ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
            // 开启一个定时任务进行不断刷新过期时间
            ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
                if (e != null) {
                    return;
                }
                // lock acquired 获得锁
                if (ttlRemaining == null) {
                	// 刷新过期时间方法,我们下一步详细说一下
                    scheduleExpirationRenewal(threadId);
            });
            return ttlRemainingFuture;

        5. 查看tryLockInnerAsync()方法

         RFuture tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand command) {
            internalLockLeaseTime = unit.toMillis(leaseTime);
        
            return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
            		  // 首先判断锁是否存在
                      "if (redis.call('exists', KEYS[1]) == 0) then " +
                      		// 存在则获取锁
                          "redis.call('hset', KEYS[1], ARGV[2], 1); " +
                          // 然后设置过期时间
                          "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                          "return nil; " +
                      "end; " +
                      // hexists查看哈希表的指定字段是否存在,存在锁并且是当前线程持有锁
                      "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                      		// hincrby自增一
                          "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
                          	// 锁的值大于1,说明是可重入锁,重置过期时间
                          "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                          "return nil; " +
                      "end; " +
                      // 锁已存在,且不是本线程,则返回过期时间ttl
                      "return redis.call('pttl', KEYS[1]);",
                        Collections.singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
        }

        6. 进入4留下的定时任务scheduleExpirationRenewal()方法

        一步步往下找源码:scheduleExpirationRenewal --->renewExpiration

        根据下面源码,定时任务刷新时间为:internalLockLeaseTime / 3,是看门狗的1/3,即为10s刷新一次

        private void renewExpiration() {
            ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());
            if (ee == null) {
                return;
            }
            
            Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
                @Override
                public void run(Timeout timeout) throws Exception {
                    ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());
                    if (ent == null) {
                        return;
                    }
                    Long threadId = ent.getFirstThreadId();
                    if (threadId == null) {
                        return;
                    }
                    
                    RFuture future = renewExpirationAsync(threadId);
                    future.onComplete((res, e) -> {
                        if (e != null) {
                            log.error("Can't update lock " + getName() + " expiration", e);
                            return;
                        }
                        
                        if (res) {
                            // reschedule itself
                            renewExpiration();
                        }
                    });
                }
            }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
            
            ee.setTimeout(task);
        }

        五、lock.lock(10, TimeUnit.SECONDS)源码分析

        1. 打开实现类

        @Override
        public void lock(long leaseTime, TimeUnit unit) {
            try {
            	// 这里的过期时间为我们输入的10
                lock(leaseTime, unit, false);
            } catch (InterruptedException e) {
                throw new IllegalStateException();
            }
        }

        2. 方法lock()实现展示,同三.3源码

        3. 直接来到尝试获得锁tryAcquireAsync()方法

        private  RFuture tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {
            // 这里进行判断如果没有设置参数leaseTime = -1,此时我们为10
            if (leaseTime != -1) {
            	// 来到此方法
                return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
            }
            // 此处省略后面内容,前面以详细说明。。。。
        }

        4. 打开tryLockInnerAsync()方法

        我们不难发现和没有传过期时间的方法一样,只不过leaseTime的值变了。

         RFuture tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand command) {
            internalLockLeaseTime = unit.toMillis(leaseTime);
        
            return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
            		  // 首先判断锁是否存在
                      "if (redis.call('exists', KEYS[1]) == 0) then " +
                      		// 存在则获取锁
                          "redis.call('hset', KEYS[1], ARGV[2], 1); " +
                          // 然后设置过期时间
                          "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                          "return nil; " +
                      "end; " +
                      // hexists查看哈希表的指定字段是否存在,存在锁并且是当前线程持有锁
                      "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                      		// hincrby自增一
                          "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
                          	// 锁的值大于1,说明是可重入锁,重置过期时间
                          "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                          "return nil; " +
                      "end; " +
                      // 锁已存在,且不是本线程,则返回过期时间ttl
                      "return redis.call('pttl', KEYS[1]);",
                        Collections.singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
        }

        六、lock.unlock()源码分析

        1. 打开方法实现

        @Override
        public void unlock() {
            try {
            	// 点击进入释放锁方法
                get(unlockAsync(Thread.currentThread().getId()));
            } catch (RedisException e) {
                if (e.getCause() instanceof IllegalMonitorStateException) {
                    throw (IllegalMonitorStateException) e.getCause();
                } else {
                    throw e;
                }
            }
            
        //        Future future = unlockAsync();
        //        future.awaitUninterruptibly();
        //        if (future.isSuccess()) {
        //            return;
        //        }
        //        if (future.cause() instanceof IllegalMonitorStateException) {
        //            throw (IllegalMonitorStateException)future.cause();
        //        }
        //        throw commandExecutor.convertException(future);
        }

        2. 打开unlockAsync()方法

        @Override
        public RFuture unlockAsync(long threadId) {
            RPromise result = new RedissonPromise();
            // 解锁方法,后面展开说
            RFuture future = unlockInnerAsync(threadId);
        	// 完成
            future.onComplete((opStatus, e) -> {
                if (e != null) {
                	// 取消到期续订
                    cancelExpirationRenewal(threadId);
                    // 将这个未来标记为失败并通知所有人
                    result.tryFailure(e);
                    return;
                }
        		// 状态为空,说明解锁的线程和当前锁不是同一个线程
                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;
                }
                
                cancelExpirationRenewal(threadId);
                result.trySuccess(null);
            });
        
            return result;
        }

        3. 打开unlockInnerAsync()方法

        protected RFuture unlockInnerAsync(long threadId) {
            return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
            		// 判断释放锁的线程和已存在锁的线程是不是同一个线程,不是返回空
                    "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
                        "return nil;" +
                    "end; " +
                    // 释放锁后,加锁次数减一
                    "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
                    // 判断剩余数量是否大于0
                    "if (counter > 0) then " +
                    	// 大于0 ,则刷新过期时间
                        "redis.call('pexpire', KEYS[1], ARGV[2]); " +
                        "return 0; " +
                    "else " +
                    	// 释放锁,删除key并发布锁释放的消息
                        "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));
        
        }                                    
                                                        本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
                                                        
        作者最新文章
        编程开发
        相关文章 更多
        php72w redis,docker php7安装php-redis
        php72w redis,docker php7安装php-redis

        在Docker环境下为PHP安装Redis扩展:手动编译需下载源码、安装phpize、配置编译并安装,生成redis.so并在php.d目录新建redis.ini加载,重启PHP-FPM;也可直接yum安装php72w-pecl-redis,免手动编译。

        php 如何配置 redis,php 如何设置redis
        php 如何配置 redis,php 如何设置redis

        配置PHP的Redis扩展:先安装Redis服务,从GitHub下载phpredis源码,用phpize编译安装,修改php.ini添加extension=redis.so,重启php-fpm或Apache,通过phpinfo验证并连接Redis。

        c++如何将结构体序列化为Redis兼容的RESP协议格式【深度】
        c++如何将结构体序列化为Redis兼容的RESP协议格式【深度】

        C++结构体无法直接序列化为RESP协议,需手动将字段映射为*N数组嵌套$lenbulkstring形式。整数需转换为字符串并加:前缀,空字符串用$0\r\n,null用$-1\r\n。长度计算需精确到字节数,特别注意二进制安全,同时确保字段顺序与结构体定义一致。

        如何在Golang架构中部署Redict作为高能数据临时存储中转站
        如何在Golang架构中部署Redict作为高能数据临时存储中转站

        Redis作为跨服务临时状态枢纽,需按消息队列、缓存和原子状态三重角色设计。选型用List实现任务分发及延迟重试,Stream用于事件广播与位点回溯。go-redis/v8需配置MinIdleConns、MaxRetries及超时。数据写入应JSON序列化并带type标签、设合理TTL、将关键字段提至顶层。消费通过原子GET+DEL或processedkey

        TP8.0 高并发下库存超卖?Redis 原子递减结合数据库乐观锁控制
        TP8.0 高并发下库存超卖?Redis 原子递减结合数据库乐观锁控制

        在高并发秒杀场景下,利用Redis的减库存原子操作预扣减库存拦截超卖请求,再通过数据库乐观锁(版本号与库存校验)确保最终一致性,异常时回滚Redis库存并借助Lua脚本保证原子性,从而有效防止库存超卖。

        如何在 Go 中利用 go-redis 执行 Lua 脚本优化逻辑
        如何在 Go 中利用 go-redis 执行 Lua 脚本优化逻辑

        在Go中使用go-redis执行Lua脚本可借助原子性解决竞态问题,通过redis.Script封装脚本并自动处理SHA1缓存与重试。需注意KEYS必须为非空切片且所有键在Cluster模式下落在同一slot,ARGV以字符串传入并由tonumber转换。返回值需先检查错误再调用Val(),避免panic。

        TP6.0 基于 Redis SetNX 实现的简单互斥锁机制【并发】
        TP6.0 基于 Redis SetNX 实现的简单互斥锁机制【并发】

        先看一个典型场景:电商秒杀、库存扣减、定时任务重复调度——这些并发冲突在 TP6 应用里屡见不鲜。用 Redis 的 SETNX 做互斥锁,确实是轻量又直接的方案,但“能用”和“稳妥”之间,隔着三层硬功夫:原子加锁、身份校验、合理超时。这三条,差一条就翻车。 很多人第一次上手,直接写 SETNX 加

        TP6.0 如何处理高并发抢购?Redis 原子锁与队列削峰详解【秒杀】
        TP6.0 如何处理高并发抢购?Redis 原子锁与队列削峰详解【秒杀】

        高并发抢购中,Redis的DECR原子操作可安全扣库存;配合think-queue异步削峰,接口仅扣库存和推消息;分布式锁需用SETNXEX加唯一标识,释放时用Lua脚本校验;成功资格后须以数据库事务双写为准,失败时补回Redis库存。

        Golang 实现基于 Redis 的延时任务队列
        Golang 实现基于 Redis 的延时任务队列

        基于Redis的ZSET实现延时队列需解决并发重复消费、失败重试混乱、精度不足及内存爆炸等问题。通过Lua脚本保证取删原子性,使用毫秒级时间戳,对临时错误采用指数退避重试,永久错误记入独立哈希表,并限制最大延迟、定期清理过期任务、使用紧凑序列化格式。

        RedisInsight怎么修改Key的过期时间 Redis操作方法【步骤】
        RedisInsight怎么修改Key的过期时间 Redis操作方法【步骤】

        操作 Redis 的时候,改个 Key 的过期时间其实是个高频场景。特别是做运维排查或者临时调试那会儿,谁也不想反复切命令行窗口、背那些命令参数。好在 RedisInsight 这个图形工具把这事儿做得很顺手——三种方式任选,总有一种合你胃口。 在动手前,先确认一下 Key 当前的 TTL 状态是个

        查看更多
        精品专题 更多
        装机必备
        装机必备

        正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

        Windows
        Windows

        正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

        PDF教程
        PDF教程

        正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

        Mac软件 更多
        讯飞智作 网页版
        讯飞智作 网页版
        Windows/ macOS/ Linux

        讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

        火山引擎 网页版
        火山引擎 网页版
        Windows/ macOS/ Linux

        火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

        Aperty macOS版
        Aperty macOS版
        macOS

        Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

        WINDOWS 更多
        讯飞智作 网页版
        讯飞智作 网页版
        Windows/ macOS/ Linux

        讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

        火山引擎 网页版
        火山引擎 网页版
        Windows/ macOS/ Linux

        火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

        PixTV 网页版
        PixTV 网页版
        Windows

        PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。

        即将离开本站
        您即将前往第三方网站,请确认是否继续?
        为什么要购买正版软件?
        01 官方授权

        授权来源透明,订单与许可信息可随时查询。

        02 即时交付

        付款成功后自动发放激活信息,支持订单追踪。

        03 中文协助

        安装、激活、迁移与续费问题都有专人响应。

        04 长期可用

        提供升级提醒与授权管理,换机时也能轻松迁移。

        网站备案号:苏ICP备2026018738号-1 联系邮箱:bd@zhengruan.com 网站地图

        Copyright ©2018-2026