Redission实现分布式锁lock()和tryLock()方法的区别

news2024/11/24 12:43:31
 lock.lock(30, TimeUnit.SECONDS); // 尝试获取锁30秒,如果获取不到则放弃
//尝试获取锁,等待5秒,持有锁10秒钟
boolean success = lock.tryLock(0, 10, TimeUnit.SECONDS);

Redisson 是一种基于 Redis 的分布式锁框架,提供了 lock()tryLock() 两种获取锁的方法。

lock() 方法是阻塞获取锁的方式,如果当前锁被其他线程持有,则当前线程会一直阻塞等待获取锁,直到获取到锁或者发生超时或中断等情况才会结束等待。该方法获取到锁之后可以保证线程对共享资源的访问是互斥的,适用于需要确保共享资源只能被一个线程访问的场景。Redisson 的 lock() 方法支持可重入锁和公平锁等特性,可以更好地满足多线程并发访问的需求。

tryLock() 方法是一种非阻塞获取锁的方式,在尝试获取锁时不会阻塞当前线程,而是立即返回获取锁的结果,如果获取成功则返回 true,否则返回 false。Redisson 的 tryLock() 方法支持加锁时间限制、等待时间限制以及可重入等特性,可以更好地控制获取锁的过程和等待时间,避免程序出现长时间无法响应等问题。

因此,两种获取锁的方式各有优缺点,在实际应用中需要根据具体场景和业务需求来选择合适的方法,以确保程序的正确性和高效性。

直接看代码例子lock.tryLock等待时间和持有时间都为0时。

    public String RedissonLock1() {
        RLock lock = redissonClient.getLock("order_lock");
        boolean success = true;
        try {
            System.out.println("获取锁前的时间:"+LocalDateTime.now());
            // 尝试获取锁,等待5秒,持有锁10秒钟
            success = lock.tryLock(0, 0, TimeUnit.SECONDS);
            // lock.lock(0, TimeUnit.SECONDS);
            System.out.println("获取锁后的时间:"+LocalDateTime.now());
            if (success) {
                System.out.println(Thread.currentThread().getName() + "获取到锁"+ LocalDateTime.now());
                // 模拟业务处理耗时
                // TimeUnit.SECONDS.sleep(3);
                // 模拟业务处理耗时 大于锁过期,可能导致非自己持有的锁被释放。
                TimeUnit.SECONDS.sleep(20);
            } else {
                System.out.println(Thread.currentThread().getName() + "未能获取到锁,已放弃尝试");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 判断当前线程是否持有锁
            if (success && lock.isHeldByCurrentThread()) {
                //释放当前锁
                lock.unlock();
                System.out.println(Thread.currentThread().getName() + "释放锁"+ LocalDateTime.now());
            }
        }
        return "";
    }
可以看到立即拿锁,拿不到立即就返回

 lock()方法的持有锁时间为0时

    public String RedissonLock2() {
        RLock lock = redissonClient.getLock("order_lock");
        boolean success = true;
        try {
            System.out.println("获取锁前的时间:"+LocalDateTime.now());
            // 尝试获取锁,等待5秒,持有锁10秒钟
           // success = lock.tryLock(0, 0, TimeUnit.SECONDS);
            lock.lock(0, TimeUnit.SECONDS);
            System.out.println("获取锁后的时间:"+LocalDateTime.now());
            // 模拟业务处理耗时
            // TimeUnit.SECONDS.sleep(3);
            // 模拟业务处理耗时 大于锁过期,可能导致非自己持有的锁被释放。
            TimeUnit.SECONDS.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 判断当前线程是否持有锁
            if (success && lock.isHeldByCurrentThread()) {
                //释放当前锁
                lock.unlock();
                System.out.println(Thread.currentThread().getName() + "释放锁"+ LocalDateTime.now());
            }
        }
        return "";
    }

 可以看到第二次获取锁时是阻塞的,等着业务处理了20s后,才获取到锁。

lock() 方法是阻塞获取锁的方式,而 tryLock() 方法是一种非阻塞获取锁的方式。

可以结合文章一起看 看懂Redisson分布式锁源码,其实并不难

看门狗续约时间默认是30秒

lock() 和 tryLock()都会用的 tryAcquireAsync(),看到区别,其实核心是leaseTime是否 > 0 

所以是否使用看门狗不算是tryLock和lock()的区别,可以看下面这5种情况。

            //走if (leaseTime > 0)逻辑
            lock.tryLock(5, 10, TimeUnit.SECONDS);
            lock.lock(10, TimeUnit.SECONDS);

            //不走if (leaseTime > 0)逻辑
            lock.tryLock(0, -1, TimeUnit.SECONDS);
            lock.lock(-1, TimeUnit.SECONDS);
            //lock()不带参数,默认leaseTime = -1
            lock.lock();

leaseTime不大于0时,会执行scheduleExpirationRenewal(threadId);

 当oldEntry == null时,执行renewExpiration(); 就是个定时器,即是看门狗核心逻辑了。

    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;
                }
                
                CompletionStage<Boolean> future = renewExpirationAsync(threadId);
                future.whenComplete((res, e) -> {
                    if (e != null) {
                        log.error("Can't update lock {} expiration", getRawName(), e);
                        EXPIRATION_RENEWAL_MAP.remove(getEntryName());
                        return;
                    }
                    
                    if (res) {
                        // reschedule itself
                        renewExpiration();
                    } else {
                        cancelExpirationRenewal(null);
                    }
                });
            }
        }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
        
        ee.setTimeout(task);
    }

 其中有下面红圈这段逻辑,也就是看门狗之所以在三分之一的时候执行的原因, 没有自定义时

 config.setLockWatchdogTimeout(10000); // 设置锁的看门狗超时时间为10秒

默认就是30秒 除以3  也就是10秒时执行。

 再看几段实现分布式锁核心的LUA脚本

1.拿锁LUA。

ChaGPT解释此段LUA:

这是 Redisson 中 RedissonLock 类实现的 tryLockInnerAsync() 方法。该方法用于尝试获取锁,它会先检查锁是否已经被其他线程持有,如果没有被持有,则将当前线程设置为锁的持有者,并返回 null;否则返回当前线程需要等待的时间,以便稍后再次尝试获取锁。

具体地说,Lua 脚本的逻辑是这样的:

  1. 如果锁不存在或者锁已经被当前线程持有(使用 hash 存储锁的持有者),则将当前线程作为锁的持有者,并且设置锁的过期时间,并返回 null。这里的代码具体操作包括使用 HEXISTS 命令检查锁的持有情况,使用 HINCRBY 命令来设置持有者和持有计数器并提高计数器的值,以及使用 PEXPIRE 命令来设置锁的过期时间。
  2. 如果锁已经被其他线程持有,则返回当前线程需要等待的时间,以便稍后再次尝试获取锁。这里使用 PTTL 命令来查询锁的过期时间,并返回给调用方。

需要注意的是,因为 tryLockInnerAsync 方法的实现是原子的,所以它可以保证在分布式环境下的正确性。同时,它也支持重入锁的功能,因为它保留了锁的持有者和持有计数器的值。

 2.看门狗续租LUA

ChaGPT解释此段LUA:

这是 Redisson 中 RedissonLock 类实现的 renewExpirationAsync(long threadId) 方法。该方法用于续期锁的过期时间,以避免锁在使用期间失效。首先,该方法会检查当前线程是否持有锁(通过 hash 存储锁的持有者),如果当前线程不是锁的持有者,则会直接返回 false 表示续期失败。如果当前线程是锁的持有者,则会将锁的过期时间重置为 internalLockLeaseTime 毫秒,并返回 true 表示续期成功。

具体来说,Lua 脚本的逻辑是这样的:

  1. 如果当前线程不是锁的持有者,则直接返回 0(false)。
  2. 如果当前线程是锁的持有者,则将锁的过期时间重置为 internalLockLeaseTime 毫秒,并返回 1(true)。

 3.解锁LUA

这是 Redisson 中 RedissonLock 类实现的 unlockInnerAsync(long threadId) 方法。该方法用于释放锁,首先它会检查当前线程是否持有锁(通过 hash 存储锁的持有者),如果当前线程不是锁的持有者,则直接返回 null;如果当前线程是锁的持有者,则将锁的计数器减一,如果减完之后计数器大于 0,则续期锁的过期时间,并返回 false 表示未成功释放锁;否则则删除该锁,并发布一个消息通知其他竞争者可以获取到该锁了,最后返回 true 表示成功释放锁。

具体来说,Lua 脚本的逻辑是这样的:

  1. 如果当前线程不是锁的持有者,则直接返回 null。
  2. 如果当前线程是锁的持有者,则使用 HINCRBY 命令将锁的计数器减一,并获取计数器的当前值。
  3. 如果锁的计数器大于 0,则使用 PEXPIRE 命令续期锁的过期时间,并返回 0(false)。
  4. 如果锁的计数器已经为 0,则使用 DEL 命令删除该锁,并使用 PUBLISH 命令发布一个消息通知其他竞争者可以获取到该锁了,最后返回 1(true)。

 4.预留了一个强制暴力解锁的方法给用户

//没有释放锁,导致整个系统的性能下降甚至故障,必须强行停止这个锁时 使用lock.forceUnlock();

这是 Redisson 中 RedissonLock 类实现的 forceUnlockAsync() 方法。该方法用于强制释放锁,取消锁定的状态。在方法内部,它会调用 cancelExpirationRenewal(null) 方法来停止看门狗线程的续期任务。然后使用 evalWriteAsync 方法执行 Lua 脚本,通过将锁的名字和订阅通道作为参数传入,删除锁并返回结果,同时发布一条消息到订阅通道上,表示这个锁被释放了。

具体地说,Lua 脚本的逻辑是这样的:

  1. 使用 DEL 命令删除锁,如果返回值为 1,则说明锁被成功删除,否则说明当前线程并没有持有这个锁,直接返回 0。
  2. 如果锁被成功删除,使用 PUBLISH 命令发布一条消息到订阅通道上,表示这个锁被释放了,并返回 1。

 在所有操作锁的地方使用LUA脚本,单线程,天然线程安全。

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

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

相关文章

国产蓝牙芯片OM6621P/HS6621系列门锁方案

在5G、物联网以及互联网家装市场的快速发展等多重因素的作用下&#xff0c;中国智能家居市场展现蓬勃发展态势。作为智能家居“入口”产品以及家庭智能安防产品的核心单品&#xff0c;智能门锁以其区别于传统机械锁更具安全性、便利性、可扩展性的优势&#xff0c;逐渐成为智能…

Java+GeoTools(开源的Java GIS工具包)快速入门-实现读取shp文件并显示

场景 GeoTools GeoTools 是一个开源的 Java GIS 工具包&#xff0c;可利用它来开发符合标准的地理信息系统。 GeoTools 提供了 OGC (Open Geospatial Consortium) 规范的一个实现来作为他们的开发。 官网地址: GeoTools The Open Source Java GIS Toolkit — GeoTools 参考…

Linux账号密码安全策略设置

前言 随着云计算厂商的兴起&#xff0c;云资源如ECS不再只有企业或者公司才会使用&#xff0c;普通人也可以自己买一台ECS来搭建自己的应用或者网站。虽然云计算厂商帮我们做了很多安全相关的工作&#xff0c;但并不代表我们的机器资源就绝对是安全的。 要知道有很多事情是云…

群策群力:组织效率,管理?沟通?协作?

你好&#xff0c;我是苏杰。今天让我们一起聊聊组织效率的话题。 团队大了&#xff0c;也能够自我造血以后&#xff0c;如何可持续发展就会成为我们关注的焦点。产品会衰退、行业有生命周期&#xff0c;但人的成长&#xff0c;以及人构成的组织&#xff0c;可以帮我们不断成功…

【大厂直通车】飞猪旅行日常实习_测开面经

📑哈喽,大家好,我是小浪;📱本专栏致力于持续更新最新各大厂面经,实习消息,招聘要求; 那么目前价格也仅仅是定到了29.9💰;非常的实惠,一杯奶茶钱🍵; 🧃对于订阅本专栏的同学们,博主在努力更新,那么最近忙于学校的考试,没来得及正常更新,非常抱歉,这几…

论文综述——DORE: Document Ordered Relation Extraction based on Generative Framework

DORE: Document Ordered Relation Extraction based on Generative Framework 文章的主要目标是对文档级的关系抽取。以往的研究主要是基于分类的研究&#xff0c;生成式关系抽取研究较少而且性能不佳。 文档级相比于句子级的关系抽取存在序列长度过长&#xff0c;以及实体定位…

消息队列选型

消息队列选型 大家好&#xff0c;我是易安&#xff01;今天我们聊下消息队列常见选型。 消息队列作用 谈选型之前我们先讲下我们为什么需要消息队列。 消息队列是一种很流行的技术&#xff0c;自从系统间开始通信时&#xff0c;消息队列就出现了。然而&#xff0c;对消息队列给…

java--时间类实例2--毫秒/秒运算(instant、ZoneDateTime)

有些时候给出毫秒值来让我们计算时间该怎么办。 文章目录 介绍[蓝桥杯 2021 省 B] 时间显示题目描述代码 蓝桥杯–航班时间 介绍 将毫秒值直接转成日期的有 new Date(毫秒)Instant.ofEpochMilli(毫秒)Instant.ofEpochSecond&#xff08;秒&#xff09;Instant.ofEpochSecond&…

k8s知识整理(继续整理中)

文章目录 k8s组件master节点kube-apiserverkube-schedulerkube-controller-manageretcd work节点kube-proxykubeletcontainer runtime add onsCoreDNSingress controller Pod常用控制器k8s pod创建调度过程k8s pod删除过程k8s 灰度发布&#xff08;金丝雀部署&#xff09;k8s 蓝…

日撸 Java 三百行day37

文章目录 说明day37 十字链表1.思路整理1.1十字链表的数据结构&#xff1a;1.2 手动模拟十字链表 2.代码分析2.1 十字链表的构造2.2 单元测试 说明 闵老师的文章链接&#xff1a; 日撸 Java 三百行&#xff08;总述&#xff09;_minfanphd的博客-CSDN博客 自己也把手敲的代码放…

ambari的kafka服务开启sasl

添加 sasl 配置⽂件 集群部署 Kafka2.2下载地址 http://archive.apache.org/dist/kafka/2.2.1/kafka_2.11-2.2.1.tgz 解压安装包 tar -zxvf kafka_2.11-2.2.1.tgz 部署略 ambari 数据kafka服务 在kafka的conf目录下创建sasl_conf目录,将kafka_client_jaas.conf/kafka_se…

深入浅出MySQL——CRUD

文章目录 表的增删改查Create单行数据全列插入多行数据指定列插入插入否则更新替换——REPLACE RetrieveSELECT 列WHERE 条件结果排序筛选分页结果 UpdateDelete删除数据截断表 插入查询结果聚合函数group bywhere和having SQL查询中各个关键字的执行先后顺序函数日期函数字符串…

SQL优化(3):order by优化

MySQL的排序&#xff0c;有两种方式&#xff1a; Using filesort : 通过表的索引或全表扫描&#xff0c;读取满足条件的数据行&#xff0c;然后在排序缓冲区sort buffer中完成排序操作&#xff0c;所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序。 Using index :…

IDEA 使用系列之 Alibaba Cloud Toolkit 一件部署

一、前文 做开发&#xff0c;免不了要往服务器部署前端后端&#xff0c;首先要用xftp把前后端所在文件夹打开&#xff0c;把jar、dist备份再上传&#xff0c;然后再打开xshell把前后端kill掉&#xff0c;然后再敲命令重新启动前后端&#xff0c;少则2、3分钟&#xff0c;多则10…

创新案例|探索 Snyk 的 PLG 团队1.6倍年度 ARR 增长背后的策略

组织架构不匹配、权责分配不清晰以及团队协作无机制是推进PLG业务面临的三大核心挑战&#xff0c;而安全软件公司Snyk以其指数级营收和估值增长的成功实践证明&#xff0c;构建合适且高效团队是助力PLG创新实现高速增长的关键&#xff0c;其经验值得借鉴。本文将通过分析Synk如…

Java+GeoTools实现WKT数据根据EPSG编码进行坐标系转换

场景 JavaGeoTools(开源的Java GIS工具包)快速入门-实现读取shp文件并显示&#xff1a; JavaGeoTools(开源的Java GIS工具包)快速入门-实现读取shp文件并显示_霸道流氓气质的博客-CSDN博客 在上面实现Java中集成Geotools之后&#xff0c;需求是将WKT数据转换成其他坐标系的W…

计算机网络-如何寻找目标主机

视频参考链接&#xff1a;计算机网络-如何寻找目标计算机&#xff1f;_哔哩哔哩_bilibili 在互联网中如果使计算机A与计算机B如何进行通信&#xff0c;又是如何找到目标的计算机主机呢&#xff1f; 首先最简单的通信就是两台计算机中间加一根网线&#xff0c;那么这两台计算机…

算法基础—哈希表散列表的构建和处理冲突

1 哈希表的构建 1. 直接寻址法 取关键字或者关键字的某个线性函数值作为哈希地址,即H(Key)Key或者H(Key)a*Keyb(a,b为整数),这种散列函数也叫做自身函数.如果H(Key)的哈希地址上已经有值了,那么就往下一个位置找,知道找到H(Key)的位置没有值了就把元素放进去. 2. 数字分析法…

idea中导入spring源码;在spring源码中添加注释

标题&#xff1a;idea中导入spring源码;在spring源码中添加注释 我是跟着他操作的&#xff0c;下文是一些补充说明&#xff1a; 这个也可以借鉴 gradle下载链接【使用网盘下载】,不过有的没有&#xff0c; gradel下载链接&#xff1a;这个比较全 1.Spring源码编译环境 spr…

股票期货模拟交易有用吗?股票期货模拟交易心得

股票期货市场为了满足新用户的需求&#xff0c;有专门的股票期货模拟交易平台&#xff0c;大家可以在这个平台上进行股票期货的模拟交易&#xff0c;这样可以通过不断总结&#xff0c;丰富我们的知识。下面整理的股票期货模拟交易实验心得&#xff0c;从股票期货模拟交易与实盘…