中间有些可能没有排版好,如果需要可以找我要彩色的PDF版
tryLock的源码解析重试机制以及其看门狗原理
编写一个测试类:
@Slf4j
@SpringBootTest
public class RedissonTest {
@Autowired
private RedissonClient redissonClient;
private RLock lock;
@BeforeEach
void setUp() {
lock = redissonClient.getLock("order"); //创建锁对象
}
@Test
void method1() throws InterruptedException {
//尝试获取锁
boolean isLock = lock.tryLock(1L, TimeUnit.SECONDS);
if(!isLock){
log.error("获取锁失败…………1");
return;
}
try {
log.info("获取锁成功…………1"); //获取锁成功
method2();
} finally {
log.info("释放锁…………1");
lock.unlock();
}
}
void method2() {
boolean isLock = lock.tryLock();
if(!isLock){
log.error("获取锁失败…………2");
return;
}
try {
log.info("获取锁成功…………2"); //获取锁成功
} finally {
log.info("释放锁…………2");
lock.unlock();
}
}
}
tryLock方法:
可传入参数有三种:
-
long
waitTime
,long
leaseTime
,TimeUnit
unit
:可重试锁-
long
waitTime
:获取锁的最大等待时长:如果设定,在第一次没有获取到锁之后,不是立刻返回,而是在最大等待时间内不断尝试,如果等待时间结束了,还没有获取到锁,则返回false -
long
leaseTime
:锁自动释放的时间,过期时间 -
TimeUnit
unit
:时间的单位
-
-
<no parameters>
:无参数 -
long time,@NotNull TimeUnit unit
:-
long time
:获取锁的最大等待时长 -
TimeUnit unit
:时间的单位
-
-
可以传入等待时间
-
不传入等待时间:默认是看门狗机制,30秒
为了研究锁重试机制,所以等待传入等待时间:lock.
tryLock(1L, TimeUnit.SECONDS)
;
进入tryLock
public boolean tryLock(long waitTime, TimeUnit unit) throws InterruptedException {
return this.tryLock(waitTime, -1L, unit);
}
leaseTime
没有传,给了默认值-1
进入tryLock
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
long time = unit.toMillis(waitTime); //等待时间传为毫秒,后续都是以毫秒来计算
long current = System.currentTimeMillis(); //获取到当前时间
long threadId = Thread.currentThread().getId(); //得到线程的Id,用于后续的锁里的标识
Long ttl = this.tryAcquire(waitTime, leaseTime, unit, threadId); //tryAcquire:尝试获取锁
if (ttl == null) {
return true;
} else {
time -= System.currentTimeMillis() - current;
if (time <= 0L) {
this.acquireFailed(waitTime, unit, threadId);
return false;
} else {
current = System.currentTimeMillis();
RFuture<RedissonLockEntrysubscribeFuture = this.subscribe(threadId);
if (!subscribeFuture.await(time, TimeUnit.MILLISECONDS)) {
if (!subscribeFuture.cancel(false)) {
subscribeFuture.onComplete((res, e) -> {
if (e == null) {
this.unsubscribe(subscribeFuture, threadId);
}
});
}
this.acquireFailed(waitTime, unit, threadId);
return false;
} else {
boolean var14;
try {
time -= System.currentTimeMillis() - current;
if (time > 0L) {
boolean var16;
do {
long currentTime = System.currentTimeMillis();
ttl = this.tryAcquire(waitTime, leaseTime, unit, threadId);
if (ttl == null) {
var16 = true;
return var16;
}
time -= System.currentTimeMillis() - currentTime;
if (time <= 0L) {
this.acquireFailed(waitTime, unit, threadId);
var16 = false;
return var16;
}
currentTime = System.currentTimeMillis();
if (ttl >= 0L && ttl < time) {
((RedissonLockEntry)subscribeFuture.getNow()).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
} else {
((RedissonLockEntry)subscribeFuture.getNow()).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
}
time -= System.currentTimeMillis() - currentTime;
} while(time > 0L);
this.acquireFailed(waitTime, unit, threadId);
var16 = false;
return var16;
}
this.acquireFailed(waitTime, unit, threadId);
var14 = false;
} finally {
this.unsubscribe(subscribeFuture, threadId);
}
return var14;
}
}
}
}
进入tryAcquire方法:获取锁的业务逻辑
private Long tryAcquire(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
return (Long)this.get(this.tryAcquireAsync(waitTime, leaseTime, unit, threadId));
}
tryAcquire方法里面调用了tryAcquireAsync
进入tryAcquireAsync方法
private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
if (leaseTime != -1L) { //先判断释放时间是否是-1
return this.tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
} else {
RFuture<Long> ttlRemainingFuture = this.tryLockInnerAsync(waitTime,
this.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(),
TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
if (e == null) {
if (ttlRemaining == null) {
this.scheduleExpirationRenewal(threadId);
}
}
});
return ttlRemainingFuture;
}
}
先判断释放时间是否是-1,没有传,默认-1;
传没传入释放时间都要走tryLockInnerAsync方法,
只不过没传的话,会给你默认值:this
.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout()
getLockWatchdogTimeout
:看门狗的超时时间
进入getLockWatchdogTimeout中可以看到,看门狗的超时时间为30s
![]()
接着进入tryLockInnerAsync方法中
<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
this.internalLockLeaseTime = unit.toMillis(leaseTime);//释放时间记录到本地的一个成员表量中
return this.evalWriteAsync(this.getName(), LongCodec.INSTANCE, command,
"if (redis.call('exists', KEYS[1]) == 0) then " + //判断锁是否存在
"redis.call('hset', KEYS[1], ARGV[2], 1); " + //写数据(hash结构)
"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); " + //锁的value进行+1
"redis.call('pexpire', KEYS[1], ARGV[1]); " + //设置过期时间
"return nil; " +
"end; " +
"return redis.call('pttl', KEYS[1]);" //抢锁失败,返回当前这把锁的失效时间
, Collections.singletonList(this.getName()), //KEY集合
this.internalLockLeaseTime, //ARGV[1]
this.getLockName(threadId)); //ARGV[2]
}
这个方法就是获取锁的源码
-
this.internalLockLeaseTime = unit.toMillis(leaseTime);
:将锁的释放时间记录到了本地的一个成员表量中internalLockLeaseTime
:内部的锁的释放时间 -
Lua脚本获取锁逻辑
-
判断锁是否存在
-
不存在
-
记录锁的标识 并且 次数+1
-
设置过期时间
-
返回 nil 获取锁成功
-
-
存在
-
判断锁的标识是不是自己的
-
是自己的
-
锁的次数+1
-
设置过期时间
-
返回 nil 获取锁成功
-
-
不是自己的
-
返回当前这把锁的剩余失效时间 获取锁失败
-
-
-
-
-
返回锁的剩余失效时间有什么用呢?
重试机制
1.把锁的剩余失效时间
返回到tryLockInnerAsync
的上一个方法
private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
if (leaseTime != -1L) { //先判断释放时间是否是-1
return this.tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
} else {
RFuture<Long> ttlRemainingFuture = this.tryLockInnerAsync(waitTime,
this.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(),
TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
if (e == null) {
if (ttlRemaining == null) {
this.scheduleExpirationRenewal(threadId);
}
}
});
return ttlRemainingFuture;
}
}
tryLockInnerAsync
的方法结果封装到了RFuture<Long> ttlRemainingFuture
(在RFuture中)
因为tryLockInner
Async
函数是一个异步函数,意思是函数执行完了,代表命令发出去了,结果那没拿到,还不清楚,所以封装到了RFuture中
最后也是返回的RFuture<Long> ttlRemainingFuture
2.把RFuture
返回到tryAcquireAsync
的上一个方法
private Long tryAcquire(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
return (Long)this.get(this.tryAcquireAsync(waitTime, leaseTime, unit, threadId));
}
这里的get就获得到了阻塞等待RFuture
的结果——>等待里面的剩余有效期
3.把等待里面的剩余有效期
返回到tryAcquire
的上一个方法
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
long time = unit.toMillis(waitTime); //等待时间传为毫秒,后续都是以毫秒来计算
long current = System.currentTimeMillis(); //获取到当前时间
long threadId = Thread.currentThread().getId(); //得到线程的Id,用于后续的锁里的标识
Long ttl = this.tryAcquire(waitTime, leaseTime, unit, threadId); //tryAcquire:尝试获取锁
if (ttl == null) {
return true;
} else {
time -= System.currentTimeMillis() - current;
if (time <= 0L) {
this.acquireFailed(waitTime, unit, threadId);
return false;
} else { //开始重试
current = System.currentTimeMillis();
RFuture<RedissonLockEntrysubscribeFuture = this.subscribe(threadId);//等待订阅消息
if (!subscribeFuture.await(time, TimeUnit.MILLISECONDS)) { //判断是否超时
if (!subscribeFuture.cancel(false)) {
subscribeFuture.onComplete((res, e) -> {
if (e == null) {
this.unsubscribe(subscribeFuture, threadId);
}
});
}
this.acquireFailed(waitTime, unit, threadId);
return false;
} else { //订阅没有超时
boolean var14;
try {
time -= System.currentTimeMillis() - current;
if (time > 0L) {
boolean var16;
do {
long currentTime = System.currentTimeMillis();
ttl = this.tryAcquire(waitTime, leaseTime, unit, threadId);
if (ttl == null) {
var16 = true;
return var16;
}
time -= System.currentTimeMillis() - currentTime;
if (time <= 0L) {
this.acquireFailed(waitTime, unit, threadId);
var16 = false;
return var16;
}
currentTime = System.currentTimeMillis();
if (ttl >= 0L && ttl < time) {
((RedissonLockEntry)subscribeFuture.getNow()).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
} else {
((RedissonLockEntry)subscribeFuture.getNow()).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
}
time -= System.currentTimeMillis() - currentTime;
} while(time > 0L);
this.acquireFailed(waitTime, unit, threadId);
var16 = false;
return var16;
}
this.acquireFailed(waitTime, unit, threadId);
var14 = false;
} finally {
this.unsubscribe(subscribeFuture, threadId);
}
return var14;
}
}
}
}
-
Long ttl
=
this
.
tryAcquire
(waitTime, leaseTime, unit, threadId);
这里经历了获取锁的逻辑,得到的结果要么是nil
(成功获取到锁);要么是锁的剩余等待时间
(获取锁失败) -
为null获取锁成功,直接返回
-
不为null,则得到锁剩余等待时间,获取锁失败,准备重试获取锁
-
time -= System.currentTimeMillis() - current;
:当前时间-才准备获取锁时的时间=获取锁过程消耗的时间,然后再用最大等待时间time
=time
-获取锁过程消耗的时间
;此时time
就是剩余的等待时间long time = unit.toMillis(waitTime); //等待时间传为毫秒,后续都是以毫秒来计算long current = System.currentTimeMillis(); //获取到当前时间 long threadId = Thread.currentThread().getId(); //得到线程的Id,用于后续的锁里的标识 Long ttl = this.tryAcquire(waitTime, leaseTime, unit, threadId); //tryAcquire:尝试获取锁 if (ttl == null) { return true; } else { time -= System.currentTimeMillis() - current;
-
判断剩余的等待时间是否>0:防止获取锁时间太长,直接超过了最大等待时间
-
<=0:获取锁失败,
this
.acquireFailed(waitTime, unit, threadId);
return false
;
-
>0:还有剩余的等待时间,开始尝试
current = System.currentTimeMillis();//获取准备开始尝试的当前的时间 RFuture<RedissonLockEntrysubscribeFuture = this.subscribe(threadId);//不会立刻尝试,订阅释放锁的消息 if (!subscribeFuture.await(time, TimeUnit.MILLISECONDS)) { //尝试等待 if (!subscribeFuture.cancel(false)) { subscribeFuture.onComplete((res, e) -> { if (e == null) { this.unsubscribe(subscribeFuture, threadId); } }); } this.acquireFailed(waitTime, unit, threadId); return false;
-
current = System.currentTimeMillis();
获取准备开始尝试的当前的时间 -
不会立即尝试,因为上一秒才获取锁失败,立马尝试获取锁,很大概率也是失败,这样会增加CPU的负担,而是采取订阅机制,
在释放锁的lua脚本中 ,会
"redis.call('publish', KEYS[2], ARGV[1]); "
发布一条消息,通知大家:锁释放了-
this
.subscribe(threadId);
:订阅释放锁的消息,因为收到释放锁的消息的时间是不确定的,所以这里的结果也是封装到了RFuture
类型
-
-
!subscribeFuture.
await
(
time
, TimeUnit.MILLISECONDS)
调用future
的await
方法尝试等待,当且仅当future
在指定的时间限制内(time:剩余的等待时间)完成时为True-
没有完成,返回为false,
!false
就是true,-
订阅超时
-
this
.
unsubscribe
(subscribeFuture, threadId);
订阅超时,取消订阅,返回false
-
-
订阅没有超时
} else { //订阅没有超时 boolean var14; try { time -= System.currentTimeMillis() - current; //得到剩余最大等待时间 if (time > 0L) { boolean var16; do { //开始重试获取锁 long currentTime = System.currentTimeMillis(); ttl = this.tryAcquire(waitTime, leaseTime, unit, threadId); if (ttl == null) { var16 = true; //获取锁成功 return var16; //true } time -= System.currentTimeMillis() - currentTime; if (time <= 0L) { this.acquireFailed(waitTime, unit, threadId); //等待时间<=0 var16 = false; return var16; //false } //继续重试 currentTime = System.currentTimeMillis(); if (ttl >= 0L && ttl < time) { ((RedissonLockEntry)subscribeFuture.getNow()) .getLatch() .tryAcquire(ttl, TimeUnit.MILLISECONDS); } else { ((RedissonLockEntry)subscribeFuture.getNow()) .getLatch() .tryAcquire(time, TimeUnit.MILLISECONDS); } time -= System.currentTimeMillis() - currentTime; } while(time > 0L); this.acquireFailed(waitTime, unit, threadId); var16 = false; return var16; } this.acquireFailed(waitTime, unit, threadId); var14 = false; } finally { this.unsubscribe(subscribeFuture, threadId); } return var14; }
-
time -= System.currentTimeMillis() - current;
得到剩余最大等待时间 -
判断剩余时间是否>0
-
不>0:
this
.acquireFailed(waitTime, unit, threadId); var14 =
false
;
-
最大等待时间>0:
-
ttl =
this
.tryAcquire(waitTime, leaseTime, unit, threadId);
重试获取锁 -
判断是否获取锁成功
-
ttl ==
null
获取锁成功,var16 = true;return var16;返回var16
(true) -
获取锁失败
-
-
-
while
(time > 0L);
判断最大等待时间是否>0-
>0: 继续尝试,循环步骤b分布式锁-Redis&Redission
-
<=0:
this
.acquireFailed(waitTime, unit, threadId);var14 =
false
;
将var14设为false,代表结束
-
-
进入finally:
this
.unsubscribe(subscribeFuture, threadId);
取消订阅 -
return
var14;
;如果执行到这里,则代表等待时间耗尽了,仍没有获取到锁,返回false
-
-
-
-
-
-
-
巧妙之处,用的是利用的是消息订阅和信号量的这种机制,不是无休止的忙等机制(while(true)),而是等他释放了锁,再去尝试,对CPU比较友好
看门狗机制
怎么确保业务时执行完释放,而不是因为阻塞ttl(剩余过期时间)释放呢?
前提:没有传入leaseTime
锁自动释放的时间,过期时间 ,即leaseTime
=-1
进入tryAcquire
private Long tryAcquire(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
return (Long)this.get(this.tryAcquireAsync(waitTime, leaseTime, unit, threadId));
}
进入tryAcquireAsync
private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
if (leaseTime != -1L) {
return this.tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
} else {
RFuture<Long> ttlRemainingFuture = this.tryLockInnerAsync(waitTime,
this.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(),
TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
if (e == null) {
if (ttlRemaining == null) {
this.scheduleExpirationRenewal(threadId);
}
}
});
return ttlRemainingFuture;
}
}
-
当
lessTime=-1
的时候,tryLockInnerAsync
方法,其中会开启看门狗机制,方法返回值得到furure
-
ttlRemainingFuture.
onComplete
,当future
完成后,有点像回调函数:当tryLockInnerAsync
函数回调成功后,执行onComplete
中的代码ttlRemainingFuture.onComplete((ttlRemaining, e) -> { if (e == null) { if (ttlRemaining == null) { this.scheduleExpirationRenewal(threadId); } } });
-
ttlRemaining
剩余有效期,ttlRemaining==null
获取锁成功 -
获取锁成功之后,需要解决有效期的问题
scheduleExpirationRenewal
(自动更新续期)
-
进入scheduleExpirationRenewal
private void scheduleExpirationRenewal(long threadId) {
RedissonLock.ExpirationEntry entry = new RedissonLock.ExpirationEntry();
RedissonLock.ExpirationEntry oldEntry =
(RedissonLock.ExpirationEntry)EXPIRATION_RENEWAL_MAP
.putIfAbsent(this.getEntryName(), entry);
if (oldEntry != null) {
oldEntry.addThreadId(threadId);
} else {
entry.addThreadId(threadId);
this.renewExpiration();
}
}
-
创建了一个
ExpirationEntry
类型的对象entry
-
放入
EXPIRATION_RENEWAL_MAP
中,key是String
类型;value是ExpirationEntry
类型private static final ConcurrentMap<String, RedissonLock.ExpirationEntry> EXPIRATION_RENEWAL_MAP = new ConcurrentHashMap()
-
EXPIRATION_RENEWAL_MAP.
putIfAbsent
(
this
.
getEntryName
(), entry);
-
getEntryName
:可以理解为当前锁的名称 -
-
EXPIRATION_RENEWAL_MAP
是静态的,可以说RedissonLock
这个类的所有实例,都可以看成这个Map,而一个Lock类将来会创建多个不同锁的实例,因为不同的业务会创建不同的锁,每一个锁都会有自己的名字,因此他们在map里面都会有唯一的key和自己唯一的entry;一个锁对应一个entry,不重复; -
当第一次来的时候,这个entry一定不存在,所以用
putIfAbsent
,如果不存在,就往里面放一个新的entry,这时候返回值就是null -
如果不是第一次,是重入的,第二次来
putIfAbsent
就返回旧的entry(第一个线程来的时候创建的entey);所以不管重入几次,将来拿到的都是同一个entry,保证同一个锁永远拿到的是同一个entry -
oldEntry !=
null
第一次以后来的线程,获得到的entry都不为null,所以直接加进来oldEntry.addThreadId(threadId);
,其实不同的线程一定是不可能拿到同一把锁的,所以第一次以后来的线程一定还是第一次的那一线程(同一线程),这是一种重入 -
不管是旧的还是新的,但是第一次来会多做一个
this
.
renewExpiration
();
(更新有效期)方法: -
所以为什么如果是
oldEntry
的话就不用调用renewExpiration
函数了,因为oldEntry
里面已经有了这个定时任务了,他已经在一直执行定时任务了,只不过把threadId
传进去 -
所以为什么如果是
oldEntry
的话就不用调用renewExpiration
函数了,因为oldEntry
里面已经有了这个定时任务了,他已经在一直执行定时任务了,只不过把threadId
传进去 -
if (oldEntry != null) { oldEntry.addThreadId(threadId); } else { entry.addThreadId(threadId); this.renewExpiration(); }
-
那什么时候才能释放呢?什么时候这个任务才会取消呢?
自然是在锁释放的时候
进入unlock
public void unlock() {
try {
this.get(this.unlockAsync(Thread.currentThread().getId()));
} catch (RedisException var2) {
if (var2.getCause() instanceof IllegalMonitorStateException) {
throw (IllegalMonitorStateException)var2.getCause();
} else {
throw var2;
}
}
}
进入unlockAsync
释放锁,清除定时任务
public RFuture<Void> unlockAsync(long threadId) {
RPromise<Void> result = new RedissonPromise();
RFuture<Boolean> future = this.unlockInnerAsync(threadId);
future.onComplete((opStatus, e) -> {
this.cancelExpirationRenewal(threadId);
if (e != null) {
result.tryFailure(e);
} else if (opStatus == null) {
IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: " + this.id + " thread-id: " + threadId);
result.tryFailure(cause);
} else {
result.trySuccess((Object)null);
}
});
return result;
}
-
RFuture<Boolean> future =
this
.
unlockInnerAsync
(threadId);
执行unlockInnerAsync
函数返回值为future
-
future.onComplete
执行cancelExpirationRenewal函数void cancelExpirationRenewal(Long threadId) { RedissonLock.ExpirationEntry task = (RedissonLock.ExpirationEntry)EXPIRATION_RENEWAL_MAP .get(this.getEntryName()); if (task != null) { if (threadId != null) { task.removeThreadId(threadId); } if (threadId == null || task.hasNoThreads()) { Timeout timeout = task.getTimeout(); if (timeout != null) { timeout.cancel(); } EXPIRATION_RENEWAL_MAP.remove(this.getEntryName()); } } }
-
先从map中取出当前这把锁的定时任务,map是静态的,根据锁的名称来取,每个锁有自己的定时任务
-
task.
removeThreadId
(threadId);
把threadId
移除 -
Timeout timeout = task.
getTimeout
();
取出Timeout
任务,然后timeout.
cancel
();
把任务取消掉 -
EXPIRATION_RENEWAL_MAP.
remove
(
this
.getEntryName());
把entry移除
-
总结
Redisson分布式锁原理:
-
可重入:利用hash结构记录线程id和重入次数
-
可重试:利用信号量和PubSub功能实现等待、唤醒,获取锁失败的重试机制
-
超时续约:利用watchDog,每隔一段时间(releaseTime/3),重置超时时间