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

Redisson-Lock-加锁原理

归档

  • GitHub: Redisson-Lock-加锁原理

Unit-Test

  • RedissonLockTest

说明

  • 源码类:RedissonLock
// 加锁入口
@Override
public void lock() { lock(-1, null, false);
}/*** 加锁实现 */
private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) {long threadId = Thread.currentThread().getId();Long ttl = tryAcquire(-1, leaseTime, unit, threadId);if (ttl == null) {return; // 加锁成功,返回}// 加锁失败进行订阅CompletableFuture<RedissonLockEntry> future = subscribe(threadId); pubSub.timeout(future);RedissonLockEntry entry;if (interruptibly) {entry = commandExecutor.getInterrupted(future);} else { // 默认进入这一步entry = commandExecutor.get(future);}try {while (true) { // 循环尝试加锁ttl = tryAcquire(-1, leaseTime, unit, threadId);// lock acquiredif (ttl == null) { // 获锁成功break;}...}} finally {// 加锁成功退出时,取消订阅unsubscribe(entry, threadId);}
}/*** 尝试获取锁 */
private Long tryAcquire(long waitTime, long leaseTime, TimeUnit unit, long threadId) {// 调用异步获取锁,get() 转换成同步return get(tryAcquireAsync(waitTime, leaseTime, unit, threadId));
}/*** 异步获取锁 */
private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {RFuture<Long> ttlRemainingFuture;if (leaseTime > 0) {ttlRemainingFuture = tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);} else { // 默认进入这一步ttlRemainingFuture = tryLockInnerAsync(waitTime, internalLockLeaseTime,TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);}CompletionStage<Long> f = ttlRemainingFuture.thenApply(ttlRemaining -> {// 获锁成功的回调// lock acquiredif (ttlRemaining == null) {if (leaseTime > 0) {internalLockLeaseTime = unit.toMillis(leaseTime);} else { // 默认进入这一步// 开启锁续期定时任务scheduleExpirationRenewal(threadId);}}return ttlRemaining;});return new CompletableFutureWrapper<>(f);
}/*** Lua 获锁实现 */
<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {return evalWriteAsync(getRawName(), LongCodec.INSTANCE, command,"if ((redis.call('exists', KEYS[1]) == 0) " + // 不存在"or (redis.call('hexists', KEYS[1], ARGV[2]) == 1)) then " + // 或是当前线程"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +"redis.call('pexpire', KEYS[1], ARGV[1]); " + // 设置过期时间,默认 30s"return nil; " + // 返回空,表示获锁成功"end; " +"return redis.call('pttl', KEYS[1]);", // 返回被抢锁的 TTLCollections.singletonList(getRawName()), unit.toMillis(leaseTime), getLockName(threadId));
}/*** 锁续约。在父类 RedissonBaseLock 里面 */
protected void scheduleExpirationRenewal(long threadId) {...try {renewExpiration(); // 续约} finally {if (Thread.currentThread().isInterrupted()) {cancelExpirationRenewal(threadId); // 线程中断,取消续约}}
}/*** 锁续约任务,循环调用。在父类 RedissonBaseLock 里面 */
private void renewExpiration() {...Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {@Overridepublic void run(Timeout timeout) throws Exception {...CompletionStage<Boolean> future = renewExpirationAsync(threadId);future.whenComplete((res, e) -> {if (e != null) { // 出现异常,不再续约EXPIRATION_RENEWAL_MAP.remove(getEntryName());return;}if (res) {renewExpiration(); // 调用自己继续续约} else {cancelExpirationRenewal(null); // 锁已不是当前线程的,取消续约}});} // internalLockLeaseTime 默认为 30s(30_000)}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS); // 每 10s 续期一次
}/*** Lua 锁续约实现 */
protected CompletionStage<Boolean> renewExpirationAsync(long threadId) {return evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +"redis.call('pexpire', KEYS[1], ARGV[1]); " + // 继续设置过期时间,默认 30s"return 1; " + // 是当前线程的"end; " +"return 0;", // 已不是当前线程的了Collections.singletonList(getRawName()),internalLockLeaseTime, getLockName(threadId));
}

流程说明

  • 加锁成功则返回,同时内部开启续约任务(每 10s 一次,续约 30s TTL)
  • 加锁失败,则订阅通道,以获知别的线程释放锁的通知

Ref

  • https://zhuanlan.zhihu.com/p/135864820

相关文章:

  • 02--MySQL数据库概述
  • 自动化开发任务:在PHP框架中实现自定义命令
  • 微信小程序-伪类选择器
  • 【少儿编程Python:趣味编程,探索未来】第一章 启航编程之旅,开启智慧之门
  • 系统思考与创新解决
  • Django 条件判断模板标签
  • 【深度学习驱动流体力学】计算流体力学openfoam-paraview与python3交互
  • 动态创建接口地址
  • 探索Agent AI智能体的未来
  • Lua迭代器详解(附加红点功能实例)
  • Java学习 - 网络IP协议簇 讲解
  • css-vxe列表中ant进度条与百分比
  • RabbitMQ消息队列 安装及基本介绍
  • STM32项目分享:家庭环境监测系统
  • 【mysql】关键词搜索实现
  • @angular/forms 源码解析之双向绑定
  • 【391天】每日项目总结系列128(2018.03.03)
  • Docker入门(二) - Dockerfile
  • Druid 在有赞的实践
  • exif信息对照
  • exports和module.exports
  • HTTP那些事
  • JavaScript中的对象个人分享
  • Java多态
  • jdbc就是这么简单
  • Promise面试题,控制异步流程
  • SpringCloud(第 039 篇)链接Mysql数据库,通过JpaRepository编写数据库访问
  • VUE es6技巧写法(持续更新中~~~)
  • 大数据与云计算学习:数据分析(二)
  • 基于 Babel 的 npm 包最小化设置
  • 利用DataURL技术在网页上显示图片
  • 容器化应用: 在阿里云搭建多节点 Openshift 集群
  • 我的面试准备过程--容器(更新中)
  • 消息队列系列二(IOT中消息队列的应用)
  • 在 Chrome DevTools 中调试 JavaScript 入门
  • MPAndroidChart 教程:Y轴 YAxis
  • 阿里云IoT边缘计算助力企业零改造实现远程运维 ...
  • 哈罗单车融资几十亿元,蚂蚁金服与春华资本加持 ...
  • 如何通过报表单元格右键控制报表跳转到不同链接地址 ...
  • ​人工智能书单(数学基础篇)
  • #14vue3生成表单并跳转到外部地址的方式
  • #HarmonyOS:Web组件的使用
  • #我与Java虚拟机的故事#连载04:一本让自己没面子的书
  • (1)(1.8) MSP(MultiWii 串行协议)(4.1 版)
  • (3)选择元素——(17)练习(Exercises)
  • (6)STL算法之转换
  • (day6) 319. 灯泡开关
  • (zz)子曾经曰过:先有司,赦小过,举贤才
  • (理论篇)httpmoudle和httphandler一览
  • (淘宝无限适配)手机端rem布局详解(转载非原创)
  • (学习日记)2024.01.09
  • .[hudsonL@cock.li].mkp勒索病毒数据怎么处理|数据解密恢复
  • .NET CORE使用Redis分布式锁续命(续期)问题
  • .NET6 开发一个检查某些状态持续多长时间的类
  • .net程序集学习心得