基于Redisson的联锁(MultiLock)

基于Redis的分布式MultiLock对象允许对Lock对象进行分组并将它们作为单个锁进行处理。每个RLock对象可能属于不同的Redisson实例。

如果获取的Redisson实例MultiLock崩溃,那么它可能永远挂在获取状态。为了避免这种情况,Redisson维护了一个锁看门狗,它会在持有者Redisson实例处于活动状态时延长锁过期时间。默认情况下,锁定看门狗超时为30s,可以通过Config.lockWatchdogTimeout设置进行更改。作者的另外一篇文章有对看门狗机制有解析:基于Redisson的可重入分布式锁

leaseTime:在指定的时间间隔后锁将自动释放

MultiLock对象的行为符合java锁规范。这意味着只有锁的拥有者线程才能解锁它,否则会抛出IllegalMonitorStateException异常。否则考虑使用RSemaphore对象。

使用示例

普通使用示例:

RLock lock1 = redisson1.getLock("lock1");
RLock lock2 = redisson2.getLock("lock2");
RLock lock3 = redisson3.getLock("lock3");RLock multiLock = anyRedisson.getMultiLock(lock1, lock2, lock3);// traditional lock method
multiLock.lock();// or acquire lock and automatically unlock it after 10 seconds
multiLock.lock(10, TimeUnit.SECONDS);// or wait for lock aquisition up to 100 seconds 
// and automatically unlock it after 10 seconds
boolean res = multiLock.tryLock(100, 10, TimeUnit.SECONDS);
if (res) {
try {
...
} finally {
multiLock.unlock();
}
}

Async接口使用的代码示例:

RLock lock1 = redisson1.getLock("lock1");
RLock lock2 = redisson2.getLock("lock2");
RLock lock3 = redisson3.getLock("lock3");RLock multiLock = anyRedisson.getMultiLock(lock1, lock2, lock3);RFuture<Void> lockFuture = multiLock.lockAsync();// or acquire lock and automatically unlock it after 10 seconds
RFuture<Void> lockFuture = multiLock.lockAsync(10, TimeUnit.SECONDS);// or wait for lock aquisition up to 100 seconds 
// and automatically unlock it after 10 seconds
RFuture<Boolean> lockFuture = multiLock.tryLockAsync(100, 10, TimeUnit.SECONDS);lockFuture.whenComplete((res, exception) -> {// ...multiLock.unlockAsync();
});

Reactive接口使用的代码示例:

RedissonReactiveClient anyRedisson = redissonClient.reactive();RLockReactive lock1 = redisson1.getLock("lock1");
RLockReactive lock2 = redisson2.getLock("lock2");
RLockReactive lock3 = redisson3.getLock("lock3");RLockReactive multiLock = anyRedisson.getMultiLock(lock1, lock2, lock3);Mono<Void> lockMono = multiLock.lock();// or acquire lock and automatically unlock it after 10 seconds
Mono<Void> lockMono = multiLock.lock(10, TimeUnit.SECONDS);// or wait for lock aquisition up to 100 seconds 
// and automatically unlock it after 10 seconds
Mono<Boolean> lockMono = multiLock.tryLock(100, 10, TimeUnit.SECONDS);lockMono.doOnNext(res -> {// ...
})
.doFinally(multiLock.unlock())
.subscribe();

RxJava3接口使用的代码示例:

RedissonRxClient anyRedisson = redissonClient.rxJava();RLockRx lock1 = redisson1.getLock("lock1");
RLockRx lock2 = redisson2.getLock("lock2");
RLockRx lock3 = redisson3.getLock("lock3");RLockRx multiLock = anyRedisson.getMultiLock(lock1, lock2, lock3);Completable lockRes = multiLock.lock();// or acquire lock and automatically unlock it after 10 seconds
Completable lockRes = multiLock.lock(10, TimeUnit.SECONDS);// or wait for lock aquisition up to 100 seconds 
// and automatically unlock it after 10 seconds
Single<Boolean> lockRes = multiLock.tryLock(100, 10, TimeUnit.SECONDS);lockRes.doOnSuccess(res -> {// ...
})
.doFinally(multiLock.unlock())
.subscribe();

源码解析(RedissonMultiLock)

  • Redisson获取联锁
// 这里相对简单,就是创建了一个RLock集合,为了后续分别去获取锁
final List<RLock> locks = new ArrayList<>();
@Override
public RLock getMultiLock(RLock... locks) {return new RedissonMultiLock(locks);
}
public RedissonMultiLock(RLock... locks) {if (locks.length == 0) {throw new IllegalArgumentException("Lock objects are not defined");}this.locks.addAll(Arrays.asList(locks));
}
  • 加锁

leaseTime:指定加锁的时间。超过这个时间后锁便自动解开了。

为了方便我们的源码分析,假设我们的locks的size为6。leaseTime为2s

@Override
public void lock(long leaseTime, TimeUnit unit) {try {lockInterruptibly(leaseTime, unit);} catch (InterruptedException e) {Thread.currentThread().interrupt();}
}@Override
public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {// 基础等待时间设置为连锁数量*1500,单位是毫秒 // 6*1500=9000ms 也就是9slong baseWaitTime = locks.size() * 1500;// 设置等待时间为-1long waitTime = -1;// 如果锁释放的时间为-1,就让等待时间等于基础等待时间9s// lock的无参方法默认leaseTime=-1if (leaseTime == -1) {waitTime = baseWaitTime;} else {// 如果锁的释放时间不为-1,把leaseTime转为毫秒leaseTime = unit.toMillis(leaseTime);// 把锁的释放时间传给等待时间,如果leaseTime=2s那么waitTime也等于2swaitTime = leaseTime;if (waitTime <= 2000) {// 也就是说leaseTime即使小于2s,waitTime也会被重置为2swaitTime = 2000;} else if (waitTime <= baseWaitTime) {// 如果leaseTime大于2s,并且小于9s,将重新设置等待时间,我们暂且还不知道这个等待时间做什么用。// 如果leaseTime等于6,那么waitTime=6,此时waitTime小于9s,重新设置waitTime// 将waitTime设置为大于等于3小于6的整数。(此处不明白看下面的解释)waitTime = ThreadLocalRandom.current().nextLong(waitTime/2, waitTime);} else {// 如果leaseTime大于2s而且大于9s(baseWaitTime),同样重新设置waitTime的值// 如果传入的leaseTime=10s,那么waitTime一开始也是10s,并且大于baseWaitTime的9s// 将waitTime设置为大于等于9s,小于10s的整数。waitTime = ThreadLocalRandom.current().nextLong(baseWaitTime, waitTime);}}while (true) {// 传入waitTime开始尝试获取锁了if (tryLock(waitTime, leaseTime, TimeUnit.MILLISECONDS)) {return;}}
}

ThreadLocalRandom.current().nextLong(origin, bound)是用于生成一个指定范围内的随机长整数。

具体解释如下:

ThreadLocalRandom.current() 返回当前线程的 ThreadLocalRandom 实例,用于生成随机数。

nextLong(origin, bound) 生成一个介于 origin(包含)和 bound(不包含)之间的随机长整型数。这意味着生成的随机数大于等于 origin,并且小于 bound。

此处为什么需要去修改waitTime的值,为什么还得整个随机数,使用baseWaitTime调整waitTime的作用是什么?

  • 尝试获取锁

waitTime:表示尝试获取锁的等待时间。它指定了在尝试获取锁时最长的等待时间。

leaseTime: 指定加锁的时间。超过这个时间后锁便自动解开了。

TimeUnit:时间单位

// 假设传入的waitTime=2s leaseTime=2s
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {// 定义了一个新的释放时间newLeaseTime=-1long newLeaseTime = -1;// 如果传入了时间的tryLock,leaseTime就不等于-1,不传默认值为-1if (leaseTime != -1) {// 将新的锁释放时间设置为waitTime的2倍,单位是毫秒,也就是4000msnewLeaseTime = unit.toMillis(waitTime)*2;}// 获取当前时间(毫秒)long time = System.currentTimeMillis();// remain==保持,先翻译为保持时间,定义为-1long remainTime = -1;if (waitTime != -1) {// 保持时间设置为waitTime,2000msremainTime = unit.toMillis(waitTime);}// calcLockWaitTime(remainTime);-->return Math.max(remainTime / locks.size(), 1);// 300ms=lockWaitTimelong lockWaitTime = calcLockWaitTime(remainTime);// return 0int failedLocksLimit = failedLocksLimit();List<RLock> acquiredLocks = new ArrayList<>(locks.size());// 循环获取锁for (ListIterator<RLock> iterator = locks.listIterator(); iterator.hasNext();) {// 获取到的redisson实例生成的锁RLock lock = iterator.next();// 锁获取标识boolean lockAcquired;try {if (waitTime == -1 && leaseTime == -1) {lockAcquired = lock.tryLock();} else {// awaitTime=300mslong awaitTime = Math.min(lockWaitTime, remainTime);// 直接去获取锁,返回true or falselockAcquired = lock.tryLock(awaitTime, newLeaseTime, TimeUnit.MILLISECONDS);}} catch (RedisResponseTimeoutException e) {// 如果发生了RedisResponseTimeoutException,会先解锁。因为这个时候不确定是否加锁成功了,所以解锁设置标识为失败。unlockInner(Arrays.asList(lock));lockAcquired = false;} catch (Exception e) {// 其他异常设置标识为falselockAcquired = false;}if (lockAcquired) {// 如果加锁成功 放入集合中acquiredLocks.add(lock);} else {// 6-当前成功的数量=0,直接退出循环,也就是说超过了最大的失败限制// 这里RedissonRedLock有重写,红锁有自己的规则if (locks.size() - acquiredLocks.size() == failedLocksLimit()) {break;}// failedLocksLimit==0,那么只要失败就进入这个逻辑if (failedLocksLimit == 0) {// 会把获取到锁的一次性解锁unlockInner(acquiredLocks);if (waitTime == -1 && leaseTime == -1) {return false;}// 重置failedLocksLimit=0failedLocksLimit = failedLocksLimit();// 清空获取到锁的集合acquiredLocks.clear();// reset iteratorwhile (iterator.hasPrevious()) {iterator.previous();}} else {// RedissonRedLock才会进入这个逻辑failedLocksLimit--;}}// 如果remainTime不为-1// remainTime=2000msif (remainTime != -1) {// 查看remainTime的剩余时间remainTime -= System.currentTimeMillis() - time;// 重置timetime = System.currentTimeMillis();// 如果保持时间也就是之前的waitTime小于0,也就是说超过了尝试获取锁时最长的等待时间,释放所有已获得的锁,并返回false,加锁失败if (remainTime <= 0) {unlockInner(acquiredLocks);return false;}}}// 如果没有超过尝试获取锁时最长等待时间,并且leaseTime不为-1if (leaseTime != -1) {// 创建了一个RFuture集合List<RFuture<Boolean>> futures = new ArrayList<>(acquiredLocks.size());for (RLock rLock : acquiredLocks) {//为每个锁设置过期时间,是一个异步的操作RFuture<Boolean> future = ((RedissonLock) rLock).expireAsync(unit.toMillis(leaseTime), TimeUnit.MILLISECONDS);futures.add(future);}for (RFuture<Boolean> rFuture : futures) {// 阻塞当前线程,同步等待每个异步操作的结果rFuture.syncUninterruptibly();}}return true;}

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

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

相关文章

管理类联考——逻辑——真题篇——按知识分类——汇总篇——二、论证逻辑——假设——第二节——搭桥假设

文章目录 第二节 假设-分类1-搭桥假设-当题干推理存在明显断点,常见形式比如:“因为A→B,C→D,所以A→D”,则正确选项为“B→C”真题(2014-39)-假设-分类1-题干推理存在明显断点-搭桥假设-建模搭桥-“因为A→B,所以A→C”,搭桥假设为“B→C”真题(2019-44)-假设-分…

ubuntu20.04 安装 Docker

sudo groupadd docker sudo usermod -a -G docker 当前用户名称 sudo mkdir -p /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg echo "deb [arch$(dpkg --print-architecture) signed…

QT的工程文件认识

目录 1、QT介绍 2、QT的特点 3、QT模块 3.1基本模块 3.2扩展模块 4、QT工程创建 1.选择应用的窗体格式 2.设置工程的名称与路径 3.设置类名 4.选择编译器 5、QT 工程解析 xxx.pro 工程配置 xxx.h 头文件 main.cpp 主函数 xxx.cpp 文件 6、纯手工创建一个QT 工程…

H 指数

H 指数 题目: 给你一个整数数组 citations &#xff0c;其中 citations[i] 表示研究者的第 i 篇论文被引用的次数。计算并返回该研究者的 h 指数。根据维基百科上 h 指数的定义&#xff1a;h 代表“高引用次数” &#xff0c;一名科研人员的 h 指数 是指他&#xff08;她&…

uniapp 回退到指定页面 保存页面状态

uniapp 历史页面回退到指定页面。 getCurrentPages() 内容如下 let delta getCurrentPages().reverse().findIndex(item > item.route "pages/popularScience/daodi") if(delta-1){uni.navigateTo({url: /pages/popularScience/daodi,success: res > {},fa…

派森 #P133. json格式转存csv文件

描述 文件movie.in中以json格式存放了一些电影数据&#xff0c;你可以通过json库很容易将数据读入成Python内部对象&#xff0c;请观察一下代码的运行结果。 import json file_name "movie_inf.in" with open(file_name,rt,encodingutf-8) as fid:txt json.load(…

《Java Web程序设计》试卷01

《Java Web程序设计》试卷01 课程编码&#xff1a; 301209 适用专业&#xff1a; 计算机应用(包括JAVA方向) 注 意 事 项 1、首先按要求在试卷标封处填写你所在的系&#xff08;部&#xff09;、专业、班级及学号和姓名&#xff1b; 2、仔细阅读各类题目的回答要求&#xff0c;…

【C++奇遇记】内存模型

&#x1f3ac; 博客主页&#xff1a;博主链接 &#x1f3a5; 本文由 M malloc 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f384; 学习专栏推荐&#xff1a;LeetCode刷题集 数据库专栏 初阶数据结构 &#x1f3c5; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如…

HexoAssistant——博客上传助手(含源码)

文章目录 HexoAssistant——博客上传助手(含源码)1 前言2 效果演示3 源码地址4 总结 HexoAssistant——博客上传助手(含源码) 1 前言 旅行之余&#xff0c;用PyQt5写了一个博客上传的工具&#xff0c;旨在更加便捷地将本地文章上传Github博客。之前虽然配置过hexogithub的博客…

机器学习:开启智能时代的重要引擎

引言 随着科技的飞速发展&#xff0c;人工智能已经渗透到我们生活的各个领域。而在人工智能的众多领域中&#xff0c;机器学习以其强大的数据处理能力和智能决策能力受到了广泛关注。本文将向您介绍机器学习的概念、工作原理、应用领域以及未来的发展前景。 一、什么是机器学…

GDAL 图像分块处理操作

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 有时候图像太大,电脑的内存不足,我们无法将整个图像读取到内存中,那么此时就需要对图像进行分块处理了。GDAL为我们提供了分块的相关接口,这里以提取最大最小值为例来演示一下图像的分块操作。 二、实现代码 /…

C++最易读手撸神经网络两隐藏层(任意Nodes每层)梯度下降230821a

// c神经网络手撸20梯度下降22_230820a.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 #include<iostream> #include<vector> #include<iomanip> // setprecision #include<sstream> // getline stof() #include<fstream…

关于打包多模块SpringBoot项目并通过宝塔上传服务器

打包 —— 如何打包多模块项目&#xff0c;参考b站up主&#xff1a;[喜欢编程的代先生] 的视频 总结&#xff1a;1. 对着视频里看一下父模块和各个子模块pom.xml文件关于打包工具的依赖是否正确。2. 从最底层开始打包&#xff0c;逐层向上&#xff0c;最后再合并打包。 部署 …

【计算机网络篇】TCP协议

✅作者简介&#xff1a;大家好&#xff0c;我是小杨 &#x1f4c3;个人主页&#xff1a;「小杨」的csdn博客 &#x1f433;希望大家多多支持&#x1f970;一起进步呀&#xff01; TCP协议 1&#xff0c;TCP 简介 TCP&#xff08;Transmission Control Protocol&#xff09;是…

uniapp,使用canvas制作一个签名版

先看效果图 我把这个做成了页面&#xff0c;没有做成组件&#xff0c;因为之前我是配合uview-plus的popup弹出层使用的&#xff0c;这种组件好像是没有生命周期的&#xff0c;第一次打开弹出层可以正常写字&#xff0c;但是关闭之后再打开就不会显示绘制的线条了&#xff0c;还…

【C语言进阶(4)】指针和数组笔试题

文章目录 Ⅰ 一维数组Ⅱ 字符数组题型 1题型 2题型 3 Ⅲ 二维数组 数组名的意义 sizeof(数组名)&#xff0c;这里的数组名表示整个数组&#xff0c;计算的是整个数组的大小。&数组名&#xff0c;这里的数组名表示的是整个数组&#xff0c;取出的是整个数组的地址。除了上述…

2023年03月 C/C++(三级)真题解析#中国电子学会#全国青少年软件编程等级考试

第1题&#xff1a;和数 给定一个正整数序列&#xff0c;判断其中有多少个数&#xff0c;等于数列中其他两个数的和。 比如&#xff0c;对于数列1 2 3 4, 这个问题的答案就是2, 因为3 2 1, 4 1 3。 时间限制&#xff1a;10000 内存限制&#xff1a;65536 输入 共两行&#x…

TCP-消息队列模型

#include "main.h" fd_win_set setSockets;VOID Server_write_error() {}int cteateserver(HWND hwnd) {WORD wVersionRequested;WSADATA wsaData;int err;/* 使用Windef.h中声明的MAKEWORD&#xff08;低字节、高字节&#xff09;宏 */wVersionRequested MAKEWORD(…

AMBA总线协议(6)——AHB(四):传输细节

一、前言 在之前的文章中&#xff0c;我们已经讲述了AHB传输中的两种情况&#xff0c;基本传输和猝发传输。我们进行一个简单的回顾&#xff0c;首先&#xff0c;开始一次传输之前主机需要向仲裁器申请获得总线的使用权限&#xff0c;然后主机给出地址和控制信号&#xff0c;根…

非计算机科班如何丝滑转码

近年来&#xff0c;很多人想要从其他行业跳槽转入计算机领域。非计算机科班如何丝滑转码&#xff1f; 方向一&#xff1a;如何规划才能实现转码&#xff1f; 对于非计算机科班的人来说&#xff0c;想要在计算机领域实现顺利的转码并不是一件容易的事情&#xff0c;但也并非不…