本篇文章摘自:https://www.jb51.net/article/149353.htm

由于时间有限,暂未验证 仅先做记录。有大家注意下哈(会尽快抽时间进行验证)

1. 基本用法

添加依赖

org.redisson

redisson

3.8.2

Config config = newConfig();

config.useClusterServers()

.setScanInterval(2000) //cluster state scan interval in milliseconds

.addNodeAddress("redis://127.0.0.1:7000", "redis://127.0.0.1:7001")

.addNodeAddress("redis://127.0.0.1:7002");

RedissonClient redisson=Redisson.create(config);

RLock lock= redisson.getLock("anyLock");

lock.lock();try{

...

}finally{

lock.unlock();

}

针对上面这段代码,重点看一下Redisson是如何基于Redis实现分布式锁的

Redisson中提供的加锁的方法有很多,但大致类似,此处只看lock()方法

2. 加锁

可以看到,调用getLock()方法后实际返回一个RedissonLock对象,在RedissonLock对象的lock()方法主要调用tryAcquire()方法

由于leaseTime == -1,于是走tryLockInnerAsync()方法,这个方法才是关键

首先,看一下evalWriteAsync方法的定义

由于leaseTime == -1,于是走tryLockInnerAsync()方法,这个方法才是关键

首先,看一下evalWriteAsync方法的定义

最后两个参数分别是keys和params

实际调用是这样的:

单独将调用的那一段摘出来看

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; " +"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +"redis.call('pexpire', KEYS[1], ARGV[1]); " +"return nil; " +"end; " +"return redis.call('pttl', KEYS[1]);",

Collections.singletonList(getName()), internalLockLeaseTime, getLockName(threadId));

结合上面的参数声明,我们可以知道,这里KEYS[1]就是getName(),ARGV[2]是getLockName(threadId)

假设前面获取锁时传的name是“abc”,假设调用的线程ID是Thread-1,假设成员变量UUID类型的id是6f0829ed-bfd3-4e6f-bba3-6f3d66cd176c

那么KEYS[1]=abc,ARGV[2]=6f0829ed-bfd3-4e6f-bba3-6f3d66cd176c:Thread-1

因此,这段脚本的意思是

1、判断有没有一个叫“abc”的key

2、如果没有,则在其下设置一个字段为“6f0829ed-bfd3-4e6f-bba3-6f3d66cd176c:Thread-1”,值为“1”的键值对 ,并设置它的过期时间

3、如果存在,则进一步判断“6f0829ed-bfd3-4e6f-bba3-6f3d66cd176c:Thread-1”是否存在,若存在,则其值加1,并重新设置过期时间

4、返回“abc”的生存时间(毫秒)

这里用的数据结构是hash,hash的结构是: key 字段1 值1 字段2 值2 。。。

用在锁这个场景下,key就表示锁的名称,也可以理解为临界资源,字段就表示当前获得锁的线程

所有竞争这把锁的线程都要判断在这个key下有没有自己线程的字段,如果没有则不能获得锁,如果有,则相当于重入,字段值加1(次数)

3. 解锁

protected RFuture unlockInnerAsync(longthreadId) {returncommandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,"if (redis.call('exists', KEYS[1]) == 0) then " +"redis.call('publish', KEYS[2], ARGV[1]); " +"return 1; " +"end;" +"if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +"return nil;" +"end; " +"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +"if (counter > 0) then " +"redis.call('pexpire', KEYS[1], ARGV[2]); " +"return 0; " +"else " +"redis.call('del', KEYS[1]); " +"redis.call('publish', KEYS[2], ARGV[1]); " +"return 1; "+"end; " +"return nil;",

Arrays.asList(getName(), getChannelName()), LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(threadId));

}

我们还是假设name=abc,假设线程ID是Thread-1

同理,我们可以知道

KEYS[1]是getName(),即KEYS[1]=abc

KEYS[2]是getChannelName(),即KEYS[2]=redisson_lock__channel:{abc}

ARGV[1]是LockPubSub.unlockMessage,即ARGV[1]=0

ARGV[2]是生存时间

ARGV[3]是getLockName(threadId),即ARGV[3]=6f0829ed-bfd3-4e6f-bba3-6f3d66cd176c:Thread-1

因此,上面脚本的意思是:

1、判断是否存在一个叫“abc”的key

2、如果不存在,向Channel中广播一条消息,广播的内容是0,并返回1

3、如果存在,进一步判断字段6f0829ed-bfd3-4e6f-bba3-6f3d66cd176c:Thread-1是否存在

4、若字段不存在,返回空,若字段存在,则字段值减1

5、若减完以后,字段值仍大于0,则返回0

6、减完后,若字段值小于或等于0,则广播一条消息,广播内容是0,并返回1;

可以猜测,广播0表示资源可用,即通知那些等待获取锁的线程现在可以获得锁了

4. 等待

以上是正常情况下获取到锁的情况,那么当无法立即获取到锁的时候怎么办呢?

再回到前面获取锁的位置

@Overridepublic void lockInterruptibly(long leaseTime, TimeUnit unit) throwsInterruptedException {long threadId =Thread.currentThread().getId();

Long ttl=tryAcquire(leaseTime, unit, threadId);//lock acquired

if (ttl == null) {return;

}//订阅

RFuture future =subscribe(threadId);

commandExecutor.syncSubscription(future);try{while (true) {

ttl=tryAcquire(leaseTime, unit, threadId);//lock acquired

if (ttl == null) {break;

}//waiting for message

if (ttl >= 0) {

getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);

}else{

getEntry(threadId).getLatch().acquire();

}

}

}finally{

unsubscribe(future, threadId);

}//get(lockAsync(leaseTime, unit));

}protected static final LockPubSub PUBSUB = newLockPubSub();protected RFuture subscribe(longthreadId) {returnPUBSUB.subscribe(getEntryName(), getChannelName(), commandExecutor.getConnectionManager().getSubscribeService());

}protected void unsubscribe(RFuture future, longthreadId) {

PUBSUB.unsubscribe(future.getNow(), getEntryName(), getChannelName(), commandExecutor.getConnectionManager().getSubscribeService());

}

这里会订阅Channel,当资源可用时可以及时知道,并抢占,防止无效的轮询而浪费资源

当资源可用用的时候,循环去尝试获取锁,由于多个线程同时去竞争资源,所以这里用了信号量,对于同一个资源只允许一个线程获得锁,其它的线程阻塞

5. 小结

6. 其它相关

@感谢原文作者的分享:https://www.jb51.net/article/149353.htm

java redisson_Java使用Redisson分布式锁实现原理相关推荐

  1. Redis进阶- Redisson分布式锁实现原理及源码解析

    文章目录 Pre 用法 Redisson分布式锁实现原理 Redisson分布式锁源码分析 redisson.getLock(lockKey) 的逻辑 redissonLock.lock()的逻辑 r ...

  2. 17、Redis、Zk分布式锁实现原理

    我们在编程有很多场景使用本地锁和分布式锁,但是是否考虑这些锁的原理是什么?本篇讨论下实现分布式锁的常见办法及他们实现原理. 一.使用锁的原则 使用本地锁和分布式锁是为了解决并发导致脏数据的场景,使用锁 ...

  3. 老夫带你深度剖析Redisson实现分布式锁的原理

    Redis实现分布式锁的原理 前面讲了Redis在实际业务场景中的应用,那么下面再来了解一下Redisson功能性场景的应用,也就是大家经常使用的分布式锁的实现场景. 引入redisson依赖 < ...

  4. 年轻人,看看 Redisson 分布式锁—可重入锁吧!太重要了

    作者 | 李祥    责编 | 张文 来源 | 企鹅杏仁技术站(ID:xingren-tech) 引言 作为后端开发,对于所谓的线程安全.高并发等一系列名词肯定都不会陌生,相关的一些概念及技术框架是面 ...

  5. Redis实战——Redisson分布式锁

    目录 1 基于Redis中setnx方法的分布式锁的问题 2 Redisson 2.1 什么是Redisson 2.2 Redisson实现分布式锁快速入门 2.3 Redisson 可重入锁原理 什 ...

  6. Redis:Redisson分布式锁的使用(推荐使用)

    Redis:Redisson分布式锁的使用(生产环境下)(推荐使用) 关键词 基于NIO的Netty框架,生产环境使用分布式锁 redisson加锁:lua脚本加锁(其他客户端自旋) 自动延时机制:启 ...

  7. Redisson分布式锁详解

    概述 setnx分布式锁的问题 重入问题 重入问题是指获得锁的线程可以再次进入到相同的锁的代码块中,可重入锁的意义在于防止死锁,比如HashTable这样的代码中,它的方法都是使用synchroniz ...

  8. 年轻人,看看Redisson分布式锁—可重入锁吧!太重要了

    1.引言 作为后端开发,对于所谓的线程安全.高并发等一系列名词肯定都不会陌生,相关的一些概念及技术框架是面试中的宠儿,也是工作中解决一些特定场景下的技术问题的银弹.今天我们就来聊聊这些银弹中的其中一枚 ...

  9. Redisson分布式锁实战-2:解决wait_time之坑

    我们一起来分析一下原因,我们获取锁之后,我们只打印了一个日志,然后从配置文件里面拿到一个hour,然后就结束了,结束之后就来到finally里边,而这个时间并没有执行SQL语句,所以他的时间会非常非常 ...

最新文章

  1. Scratch青少年编程能力等级测试模拟题(三级)
  2. python转行it好学吗-想转行学python过来人提醒大家几点
  3. vivo X21低调奢华 彭于晏携手黑金版来袭
  4. C++容器 bitset
  5. 深入浅出多网卡绑定技术
  6. Stack Overflow 2021开发者调查报告 - 数据库篇!
  7. 各种机器学习的应用场景分别是什么
  8. 安卓脚本用什么写_python脚本控制安卓手机,可以用来做什么你知道吗?
  9. 实验一 分治与递归—用分治法实现元素选择 java算法
  10. VS2019配置opencv-4.5.2-vc14_vc15
  11. 以太网协议号字段定义
  12. 清华紫光输入法linux,清华紫光输入法
  13. 遍历文件夹批量重命名
  14. linux mysql backdoor_Linux SSH Backdoor分析排查
  15. 一台计算机有两个用户怎样共享,两台计算机如何共享一台打印机?
  16. 数据分析师的能力和目标的个人总结
  17. Javascript 返回指定日期所在星期的开始和结束时间戳,即自然周周一00:00:00.000——自然周周日23:59:59.999
  18. CMS模板标签设计使用说明
  19. C语言指针中P、*P、P、**P的区别
  20. Vue环境搭建及第一个hello world

热门文章

  1. 对于analyze table使用
  2. Kubernetes容器集群 - harbor仓库高可用集群部署说明
  3. IT知识架构和操作系统简介1
  4. script标签属性sync和defer
  5. ssh终端远程登陆主机命令--笔记
  6. WebAdaptor Object reference not set to an instance of an object.
  7. Socket阻塞与非阻塞,同步与异步、I/O模型
  8. 大四中软实习笔记20130226
  9. 微软官方的SqlHelper
  10. 使用asp.net中的跟踪功能