Redisson分布式锁基本使用及可重入锁原理分析

news2024/11/26 4:11:57

基本使用

1、引入依赖

		<!--    redisson依赖    -->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.13.6</version>
        </dependency>

2、配置redisson客户端

	// 将RedissonClient对象注入IOC容器
    @Bean
    public RedissonClient redissonClient(){
        Config config = new Config();
        // 配置单节点redis地址
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        return Redisson.create(config);
    }
@Resource
RedissonClient redissonClient;

// ----
// 获取可重入锁
RLock lock = redisson.getLock("anyLock");
// 最常见的使用方法
lock.lock();

// (无参)尝试获取锁
boolean isLock = lock.tryLock();

// 重载方法1 tryLock(Long maxWaitTime,Long timeoutRelease,TimeUnit.SECONDS)
// 参数1:锁的最大等待时间(期间会重试)
// 参数2: 锁超时释放时间
// 参数3:时间单位
boolean isLock = lock.tryLock(1,10,TimeUnit.SECONDS);

// 重载方法2 tryLock(Long maxWaitTime,TimeUnit.SECONDS)
// 参数1:锁的最大等待时间(期间会重试)
// 参数2:时间单位
boolean isLock = lock.tryLock(1,TimeUnit.SECONDS);

// 释放锁
lock.unlock();

tryLock

trylock具有返回值,true或者false,表示是否成功获取锁。tryLock前期获取锁逻辑基本与lock一致,主要是后续获取锁失败的处理逻辑与lock不一致。
trylock方法

public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
        long time = unit.toMillis(waitTime);
        long current = System.currentTimeMillis();
        long threadId = Thread.currentThread().getId();
        // 获取剩余过期时间
        Long ttl = this.tryAcquire(waitTime, leaseTime, unit, threadId);
        // 为null,则获取锁成功
        if (ttl == null) {
            return true;
        } else {
            time -= System.currentTimeMillis() - current;
            // 获取锁失败后,中途tryLock会一直判断中间操作耗时是否已经消耗锁的过期时间,如果消耗完则返回false
            if (time <= 0L) {
                this.acquireFailed(waitTime, unit, threadId);
                return false;
            } else {
                current = System.currentTimeMillis();
               // 此处为订阅锁释放事件,
               // 如果当前线程通过 Redis 的 channel 订阅锁的释放事件获取得知已经被释放
               // 则会发消息通知待等待的线程进行竞争.
                RFuture<RedissonLockEntry> subscribeFuture = this.subscribe(threadId);
                // 等待释放锁,若超过剩余等待时间,则取消订阅,
                // 并返回false,获取锁失败
                if (!subscribeFuture.await(time, TimeUnit.MILLISECONDS)) {
                    if (!subscribeFuture.cancel(false)) {
                        subscribeFuture.onComplete((res, e) -> {
                            if (e == null) {
                                this.unsubscribe(subscribeFuture, threadId);
                            }

                        });
                    }

                    this.acquireFailed(waitTime, unit, threadId);
                    return false;
                } else {
                    try {
                     // 减去订阅等待的时间,如果消耗完则返回false
                        time -= System.currentTimeMillis() - current;
                        if (time <= 0L) {
                            this.acquireFailed(waitTime, unit, threadId);
                            boolean var20 = false;
                            return var20;
                        } else {
                            boolean var16;
                            do {
                                long currentTime = System.currentTimeMillis();
                                ttl = this.tryAcquire(waitTime, leaseTime, unit, threadId);
                                if (ttl == null) {
                                    var16 = true;
                                    return var16;
                                }

                                time -= System.currentTimeMillis() - currentTime;
                                if (time <= 0L) {
                                    this.acquireFailed(waitTime, unit, threadId);
                                    var16 = false;
                                    return var16;
                                }
								
                                currentTime = System.currentTimeMillis();
                                // 尝试使用信号量的方式获取锁
                                // 如果ttl < time ;则使用ttl的时间尝试获取锁
                                if (ttl >= 0L && ttl < time) {
                                    ((RedissonLockEntry)subscribeFuture.getNow()).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                                // 如果ttl > time ;则使用time 的时间尝试获取锁
                                } else {
                                    ((RedissonLockEntry)subscribeFuture.getNow()).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
                                }

                                time -= System.currentTimeMillis() - currentTime;
                            // time时间大于0,继续尝试获取锁    
                            } while(time > 0L);

                            this.acquireFailed(waitTime, unit, threadId);
                            var16 = false;
                            return var16;
                        }
                    } finally {
                        this.unsubscribe(subscribeFuture, threadId);
                    }
                }
            }
        }
    }

tryLock的lua脚本

    <T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
        this.internalLockLeaseTime = unit.toMillis(leaseTime);
        return this.evalWriteAsync(this.getName(), LongCodec.INSTANCE, command,
                // KEYS[1] :锁的key
                // ARGV[1] :设置锁的过期时间
                // ARGV[2] :threadId
                // 判断锁是否存在,若不存在
                "if (redis.call('exists', KEYS[1]) == 0) then\n" +
                // 创建hash表并使field属性次数加一
                "redis.call('hincrby', KEYS[1], ARGV[2], 1);\n" +
                // 设置过期时间
                "redis.call('pexpire', KEYS[1], ARGV[1]);\n" +
                 // 返回结果
                "return nil;\n" +
                "end;\n" +
                 // 锁存在,判断hash的指定字段是否存在,若存在
                "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then" +
                 //  hash的field属性次数加一
                "redis.call('hincrby', KEYS[1], ARGV[2], 1);\n" +
                 // 设置过期时间
                "redis.call('pexpire', KEYS[1], ARGV[1]);\n" +
                 // 返回结果
                "return nil;\n" +
                "end;\n" +
                // 若代码走到这里,说明锁不是当前线程的,获取锁失败并返回锁的剩余过期时间(ms)
                "return redis.call('pttl', KEYS[1]);", Collections.singletonList(this.getName()), this.internalLockLeaseTime, this.getLockName(threadId));
    }

hash结构
在这里插入图片描述

上述lua脚本的流程图
在这里插入图片描述

可重试

超时续约

看门狗机制开始条件 leaseTime = -1

    private RFuture<Boolean> tryAcquireOnceAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
        if (leaseTime != -1L) {
            return this.tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
        } else {
            RFuture<Boolean> ttlRemainingFuture = this.tryLockInnerAsync(waitTime, this.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
            ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
                if (e == null) {
                	// 获取锁成功,锁的过期时间续约
                    if (ttlRemaining) {
                        this.scheduleExpirationRenewal(threadId);
                    }

                }
            });
            return ttlRemainingFuture;
        }
    }

scheduleExpirationRenewal方法


 private void scheduleExpirationRenewal(long threadId) {
        ExpirationEntry entry = new ExpirationEntry();
        // 如果entry不存在,才添加,返回null
        // 如果entry不存在,不添加进去,返回oldEntry 
        // 这样使得锁重入
        ExpirationEntry oldEntry = (ExpirationEntry)EXPIRATION_RENEWAL_MAP.putIfAbsent(this.getEntryName(), entry);
        if (oldEntry != null) {
            oldEntry.addThreadId(threadId);
        } else {
            entry.addThreadId(threadId);
            // 第一次来,添加更新有效期定时任务
            this.renewExpiration();
        }

    }

renewExpiration方法

private void renewExpiration() {
        ExpirationEntry ee = (ExpirationEntry)EXPIRATION_RENEWAL_MAP.get(this.getEntryName());
        if (ee != null) {
        	// 定时任务
            Timeout task = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
                public void run(Timeout timeout) throws Exception {
                    ExpirationEntry ent = (ExpirationEntry)RedissonLock.EXPIRATION_RENEWAL_MAP.get(RedissonLock.this.getEntryName());
                    if (ent != null) {
                        Long threadId = ent.getFirstThreadId();
                        if (threadId != null) {
                            RFuture<Boolean> future = RedissonLock.this.renewExpirationAsync(threadId);
                            future.onComplete((res, e) -> {
                                if (e != null) {
                                    RedissonLock.log.error("Can't update lock " + RedissonLock.this.getName() + " expiration", e);
                                } else {
                                
                                    if (res) {
											// 递归                                        
											RedissonLock.this.renewExpiration();
                                    }

                                }
                            });
                        }
                    }
                }
                //internalLockLeaseTime = 30 * 1000
                // 10s后执行该任务   
            }, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS);
            ee.setTimeout(task);
        }
    }

在这里插入图片描述
renewExpirationAsync方法

    protected RFuture<Boolean> renewExpirationAsync(long threadId) {
        return this.evalWriteAsync(this.getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
        		// 判断这个锁是否是当前线程的
                "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                // 重置锁的有效期
                "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                "return 1; " +
                "end;" +
                "return 0;", Collections.singletonList(this.getName()), this.internalLockLeaseTime, this.getLockName(threadId));
    }

unlock

public RFuture<Void> unlockAsync(long threadId) {
        RPromise<Void> result = new RedissonPromise();
        RFuture<Boolean> future = this.unlockInnerAsync(threadId);
        future.onComplete((opStatus, e) -> {
        	// 取消定时任务
            this.cancelExpirationRenewal(threadId);
            if (e != null) {
                result.tryFailure(e);
            } else if (opStatus == null) {
                IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: " + this.id + " thread-id: " + threadId);
                result.tryFailure(cause);
            } else {
                result.trySuccess((Object)null);
            }
        });
        return result;
    }

cancelExpirationRenewal方法

void cancelExpirationRenewal(Long threadId) {
		// 从map取出Entry
        ExpirationEntry task = (ExpirationEntry)EXPIRATION_RENEWAL_MAP.get(this.getEntryName());
        if (task != null) {
            if (threadId != null) {
	            // 删除threadId
                task.removeThreadId(threadId);
            }

            if (threadId == null || task.hasNoThreads()) {
                Timeout timeout = task.getTimeout();
                if (timeout != null) {
                	// 取消定时任务
                    timeout.cancel();
                }

    			//从map中删除entry
                EXPIRATION_RENEWAL_MAP.remove(this.getEntryName());
            }

        }
    }

unlock的lua脚本

protected RFuture<Boolean> unlockInnerAsync(long threadId) {
        return this.evalWriteAsync(this.getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                /** KEYS[1] :锁的key
                 *  KEYS[2] :锁的key
                 *  ARGV[1]
                 *  ARGV[2] :锁的有效期
                 *  ARGV[3] :threadId
                */
                // 判断锁是否是当前线程的,若等于0,说明锁不存在,直接结束
                "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
                "return nil;" +
                "end;" +
                // 锁存在,hash的field属性次数减一
                "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1);" +
                // 判断field值是否大于0,
                "if (counter > 0) then" +
                // >0 重置有效期
                "redis.call('pexpire', KEYS[1], ARGV[2]);" +
                "return 0;" +
                // <=0 删除锁
                "else" +
                "redis.call('del', KEYS[1]);" +
                // 消息通知等待的线程进行竞争
                "redis.call('publish', KEYS[2], ARGV[1]);" +
                "return 1;" +
                "end;" +
                "return nil;"
                , Arrays.asList(this.getName(), this.getChannelName()), LockPubSub.UNLOCK_MESSAGE, this.internalLockLeaseTime, this.getLockName(threadId));
    }

trylock和unlock流程图

在这里插入图片描述

总结

redisson分布式锁原理:

  • 可重入:使用hash结构记录线程id和重入次数
    获取锁
    1、判断锁是否存在,不存在,获取锁成功
    2、若锁存在,判断field是不是当前线程的,若是,value++;若不是,获取锁失败;
    释放锁
    1、判断field是不是当前线程的,若是,value–,直到value为0,删除锁;

  • 可重试:利用信号量和redis的pubsub功能实现等待、唤醒、获取锁失败的重试机制(不是无限制的等待,有等待时间)

  • 超时续约: watchDog机制,每隔一段时间(this.internalLockLeaseTime / 3L),重置过期时间
    ps:internalLockLeaseTime = 30 * 1000 ms

本文的图片来源:https://www.bilibili.com/video/BV1cr4y1671t?p=67&spm_id_from=pageDriver&vd_source=3635fc6ba1824ef476d69a1c58b3e2ec

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/345828.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【每日随笔】手指训练 ( 手指训练作用 | 哪些人需要手指训练 | 手指操 | 手指康复训练器材 )

文章目录一、手指训练作用二、哪些人需要手指训练三、手指操四、手指康复训练器材产品需求探索 , 研究下手指训练的市场 , 前景 , 是否可以开发 ; 一、手指训练作用 手指训练作用 : 改善 上肢协调性手眼 协调性训练提高 手指 抓握 能力提高 手指 灵活性提高 上肢运动 准确性 和…

vscode下进行python配置及编码,新手学习

介绍在开发工具vscode下进行python的配置及编码&#xff0c;新手来学。 1、首先下载及安装最新版本的vscode。 【VSCode最新版本下载安装详细教程&#xff08;win10)】 2、下载安装最新版本python&#xff0c;也可以根据需要下载自己需要的版本。 【python3.11下载安装详细…

基于Web的6个完美3D图形WebGL库

现代前端、游戏和Web开发正是WebGL可以转化为数字杰作的东西。使用GPU绘制在浏览器屏幕上生成的矢量元素&#xff0c;WebGL创建交互式Web图形&#xff0c;从而获得用户体验。视觉元素的质量和复杂性使该工具在HTML或CSS等其他方法中脱颖而出。WebGL基础WebGL不是一个图形套件。…

电脑录屏用什么软件?推荐这3款软件,用过都说好

如今网络中&#xff0c;有很多的软件都能够实现电脑录屏。但想要找一个方便好用的电脑录屏软件却十分困难。电脑录屏用什么软件&#xff1f;今天小编将为小伙伴分享3款超级方便好用的电脑录屏软件&#xff0c;用过的小伙伴都说好&#xff01;一起来看看吧。 电脑录屏软件1&…

即时通讯为组织构建移动平台的规划和效益分析

随着移动通信、移动互联网、云计算等新技术的快速发展&#xff0c;移动信息化已成为应用软件发展的重要趋势。传统的固定办公模式&#xff0c;限制了工作人员办事的灵活性&#xff0c;降低了办事效率&#xff1b;随着办事“移动性”越来越高&#xff0c;出差越来越频繁&#xf…

线程互斥、同步

目录 一、线程互斥 1.1 相关概念介绍 1.2 互斥量mutex 1.3 互斥量接口 1.4 互斥量实现原理 二、可重入与线程安全 2.1 概念 2.2 常见线程不安全的情况 2.3 常见线程安全的情况 2.4 常见不可重入的情况 2.5 常见可重入的情况 2.6 可重入与线程安全的关系 三、死锁 …

MySql分表、分库、分片和分区的区别

一、前言 数据库的数据量达到一定程度之后&#xff0c;为避免带来系统性能上的瓶颈。需要进行数据的处理&#xff0c;采用的手段是分区、分片、分库、分表。 二、分片&#xff08;类似分库&#xff09; 分片是把数据库横向扩展&#xff08;Scale Out&#xff09;到多个物理节…

链表题目总结 -- 迭代

目录一. 反转链表1. 思路简述2. 代码3. 总结二. K 个一组翻转链表1. 思路简述2. 代码3. 总结一. 反转链表 题目链接&#xff1a;https://leetcode.cn/problems/reverse-linked-list/ 1. 思路简述 三个指针进行迭代&#xff0c;pre指向前驱&#xff0c;cur是当前指针&#xf…

Python requests模块

一、requests模块简介 requests模块是一个第三方模块&#xff0c;需要在python环境中安装&#xff1a; pip install requests 该模块主要用来发送 HTTP 请求&#xff0c;requests 模块比 urllib 模块更简洁。 requests模块支持&#xff1a; 自动处理url编码自动处理post请求…

软件工程(5)--喷泉模型

前言 这是基于我所学习的软件工程课程总结的第五篇文章。 迭代是软件开发过程中普遍存在的一种内在属性。经验表明&#xff0c;软件过程各个阶段之间的迭代或一个阶段内各个工作步骤之间的迭代&#xff0c;在面向对象范型中比在结构化范型中更常见。 一般说来&#xff0c;使用…

新闻稿怎么发?影响发稿价格的因素

在当今的信息化社会&#xff0c;新闻稿是传播信息的重要手段&#xff0c;在企业、大众媒体、政府机构以及其他组织中都广泛使用新闻稿来向外界发布信息。但是&#xff0c;发布新闻稿既要求新闻稿内容准确&#xff0c;又要求要有良好的发布形式&#xff0c;这就要求发布者要懂得…

专用舆情监测服务公司,TOOM舆情监测公司排名全国

舆情监测公司是专门从互联网、传媒等渠道搜集信息&#xff0c;对与客户相关的信息进行筛选、分析和报告&#xff0c;帮助客户及时了解和掌握舆情动态&#xff0c;保障客户的声誉和利益&#xff0c;专用舆情监测服务公司&#xff0c;TOOM舆情监测公司排名全国。 一、专用舆情监…

Win10系统不需要的服务项怎么删除?

Win10系统不需要的服务项怎么删除&#xff1f;在使用电脑的时候&#xff0c;里面会有一些不需要的服务项目。比如一些我们以前安装的软件&#xff0c;卸载之后的服务项残留等等。那么如何去进行不需要的服务项删除呢&#xff1f;一起来看看具体的方法分享吧。 操作教程 1、首先…

【C++提高笔记】泛型编程与STL技术

文章目录模板的概念函数模板函数模板语法函数模板注意事项函数模板案例普通函数与函数模板的调用规则模板的局限性类模板类模板语法类模板与函数模板区别类模板中成员函数创建时机类模板对象做函数参数类模板与继承类模板成员函数类外实现类模板分文件编写类模板与友元类模板案…

从源码角度解析SpringMVC执行流程

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 SpringMVC执行流程在面试中经常会被问到&#xff0c;本篇文章通过源码的方式简单的了解一下SpringMVC执行流程。 先看流程 先看一下Sp…

基于java的一款实时聊天系统,包含服务端 + 客户端 + web端

最终效果为什么先看最终效果&#xff1f;因为此刻代码已经撸完了。更重要的是我们带着感官的目标去进行后续的分析&#xff0c;可以更好地理解。标题中提到了&#xff0c;整个工程包含三个部分&#xff1a;1、聊天服务器聊天服务器的职责一句话解释&#xff1a;负责接收所有用户…

图像去噪技术简述

随着每天拍摄的数字图像数量激增&#xff0c;对更准确、更美观的图像的需求也在增加。然而&#xff0c;现代相机拍摄的图像不可避免地会受到噪声的影响&#xff0c;从而导致视觉图像质量下降。因此&#xff0c;需要在不丢失图像特征&#xff08;边缘、角和其他尖锐结构&#xf…

基于RK3399+YOLO目标检测人工智能图像系统设计

随着 5G 通信技术的大范围普及&#xff0c;传统的目标检测系统已经不再能满足如今步入智 能化的各行各业的需求。消费者对以往依靠机器学习的传统目标检测系统提出了更高的 要求&#xff0c;相关的生产企业也开始向智能化和低功耗化过渡。其中如何将图像增强技术、目 标检测技术…

报表生成工具Stimulsoft中的电子签名和 PDF 数字签名

Stimulsoft Reports 是一款报告编写器&#xff0c;主要用于在桌面和Web上从头开始创建任何复杂的报告。可以在大多数平台上轻松实现部署&#xff0c;如ASP.NET, WinForms, .NET Core, JavaScript, WPF, Angular, Blazor, PHP, Java等&#xff0c;在你的应用程序中嵌入报告设计器…

炔基染料试剂1998119-13-3,Cyanine7 alkyne,花青素CY7炔基

试剂基团反应特点&#xff1a;Cyanine7 alkyne染料在水中的溶解度有限&#xff0c;但可以通过添加二甲基亚砜或二甲基甲酰胺在水性缓冲液中成功结合。氰基7的炔烃衍生物&#xff0c;近红外荧光团&#xff0c;Cy7的类似物。炔烃可以通过铜催化的点击化学与多种叠氮化合物共轭。C…