redis实现分布式可重入锁
2022-03-09 09:39:22 TwelveT 516
可重入当一个线程执行一段代码成功获取锁之后,继续执行时,又遇到加锁的代码,可重入性就就保证线程能继续执行,而不可重入就是需要等待锁释放之后,再次获取锁成功,才能继续往下执行。用一段Java代码解释可重入:publicsynchronizedvoida(){ b();}publicsynchronizedvoidb(...
可重入
当一个线程执行一段代码成功获取锁之后,继续执行时,又遇到加锁的代码,可重入性就就保证线程能继续执行,而不可重入就是需要等待锁释放之后,再次获取锁成功,才能继续往下执行。用一段 Java 代码解释可重入:
public synchronized void a() {
b();
}
public synchronized void b() {
// pass
}
假设 X 线程在 a 方法获取锁之后,继续执行 b 方法,如果此时不可重入,线程就必须等待锁释放,再次争抢锁。锁明明是被 X 线程拥有,却还需要等待自己释放锁,然后再去抢锁,这看起来就很奇怪,我释放我自己~
ReentrantLock可重入锁源码思路
加锁
/**
* ReentrantLock的加锁流程是:
* 1,先判断是否有线程持有锁,没有加锁进行加锁
* 2、如果加锁成功,则设置持有锁的线程是当前线程
* 3、如果有线程持有了锁,则再去判断,是否是当前线程持有了锁
* 4、如果是当前线程持有锁,则加锁数量(state)+1
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//先判断,c(state)是否等于0,如果等于0,说明没有线程持有锁
if (c == 0) {
//通过cas方法把state的值0替换成1,替换成功说明加锁成功
if (compareAndSetState(0, acquires)) {
//如果加锁成功,设置持有锁的线程是当前线程
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) {//判断当前持有锁的线程是否是当前线程
//如果是当前线程,则state值加acquires,代表了当前线程加锁了多少次
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
解锁
/**
* 看ReentrantLock的解锁代码我们知道,每次释放锁的时候都对state减1,
* 当c值等于0的时候,说明锁重入次数也为0了,
* 最终设置当前持有锁的线程为null,state也设置为0,锁就释放了。
*/
protected final boolean tryRelease(int releases) {
int c = getState() - releases;//state-1 减加锁次数
//如果持有锁的线程,不是当前线程,抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {//如果c==0了说明当前线程,已经要释放锁了
free = true;
setExclusiveOwnerThread(null);//设置当前持有锁的线程为null
}
setState(c);//设置c的值
return free;
}
分布式重入锁(redisson依然实现,但此处注重原理实现)
基于ReentrantLock的源码我们知道,它是加锁成功了,记录了当前持有锁的线程,并通过一个int类型的数字,来记录了加锁次数。我们知道ReentrantLock的实现原理了,那么redis只要下面两个问题解决,就能实现重入锁了:
1.如何保存现有的线程
ReentrantLock使用的是当前线程内存地址进行对比,那么我们就可以使用线程的ID进行比较一样可以的。但是在分布式环境下,这个ID就可能会存在重复,此时,我们需要增加一个全局的唯一ID + 线程ID来做一个分布式线程比较。
2.加锁次数(重入了多少次),怎么记录维护
- 他能记录下来加锁次数吗?
- 存没问题了,但是重入次数要怎么维护了, 它肯定要保证原子性的,能解决吗?
第一步:先获取到valus值,把取到加锁次数+1
第二部:把新的值再设置进去
在执行第二步操作之前,如果这个key失效了(设置持有锁超时了),如果还能再设置进去,就会有并发问题了
Redisson是如何实现的
我们跟一下lock.lock()的代码,发现它最终调用的是org.redisson.RedissonLock#tryLockInnerAsync的方法,具体如下:
<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) then " +
"redis.call('hincrby', 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(getRawName()), unit.toMillis(leaseTime), getLockName(threadId));
}
分析一下redis命令
- exists 查询一个key是否存在
- hincrby :将hash中指定域的值增加给定的数字
- pexpire:设置key的有效时间以毫秒为单位
- hexists:判断field是否存在于hash中
- pttl:获取key的有效毫秒数
-
看lua脚本传入的参数我们知道
- KEYS[1] = key的值
- ARGV[1]) = 持有锁的时间
- ARGV[2] = getLockName(threadId) 下面id就算系统在启动的时候会全局生成的uuid 来作为当前进程的id,加上线程id就是getLockName(threadId)了,可以理解为:进程ID+系统ID = ARGV[2]
现在我们来看下lua脚本的加锁流程

1、第一个if判断
204行:它是先判断了当前key是否存在,从EXISTS命令我们知道返回值是0说明key不存在,说明没有加锁205行:hincrby命令是对 ARGV[2] = 进程ID+系统ID 进行原子自增加1
206行:是对整个hash设置过期期间
2、下面来看第二个if判断
209行:判断field是否存在于hash中,如果存在返回1,返回1说明是当前进程+当前线程ID 之前已经获得到锁了210行:hincrby命令是对 ARGV[2] = 进程ID+系统ID 进行原子自增加1,说明重入次数加1了
211行:再对整个hash设置过期期间
注意:分布式锁有过期时间,默认-1的时候是需要自动续期的(redisson有看门狗自动续期),当服务down机后自然自动释放
解锁过程和Reentrantlock的解锁逻辑也基本相同没啥好说的了
免责声明
本站提供的一切软件、教程和内容信息仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络收集整理,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。如果您喜欢该程序和内容,请支持正版,购买注册,得到更好的正版服务。我们非常重视版权问题,如有侵权请邮件与我们联系处理。敬请谅解!
![]() |
相关文章
-
02-26微服务分布式事务如何实现
-
02-26如何实现接口的幂等性,多次操作结果均为一致
-
01-18windows安装redis并设置开机启动
-
01-17单例模式(单例设计模式)详解
-
01-17GoF 的 23 种设计模式的分类和功能
-
01-08Centos7安装k8s集群
-
11-22centos7设置静态IP,以及解决无法上网,亲测!!!
-
11-09elasticsearch实现pinyin + 中文搜索,模糊like搜索
-
11-09最新版elasticsearch实现mysql中的模糊搜索like,金字塔切割法ngram
-
10-30docker启动elasticsearch + kibana + ik分词器 + pinyin分词器