1、可重试机制源码分析
// tryLock方法
/**
*
* @param waitTime 当前线程在waitTime内未获取到锁,则会不断重试,而不是直接返回true或者false
* @param leaseTime 锁超时释放时间
* @param unit 时间单位
* @return
* @throws InterruptedException
*/
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();
//距离锁释放的剩余时间(tryAcquire返回Null说明获取到锁,未返回null而是返回当前锁距离释放的剩余时间则表示未获取到锁)
Long ttl = this.tryAcquire(leaseTime, unit, threadId);
/**
* 获取到锁
*/
if (ttl == null) {
return true;
} else {
/**
* 没获取到锁
*/
time -= System.currentTimeMillis() - current;
if (time <= 0L) {
//没获取到锁,且当前需要等待的时间为0了,直接返回false
this.acquireFailed(threadId);
return false;
} else {
//还有剩余等待时间需要去等待
current = System.currentTimeMillis();
//不需要立即尝试(以免浪费资源),则其订阅其他线程释放锁的信号。因其他线程释放锁后,会publish一个信号
RFuture<RedissonLockEntry> subscribeFuture = this.subscribe(threadId);
//由于收到信号的时间不确定,所以执行等待(await)。若等待time结束还未收到通知,则进入if快,且取消订阅
if (!subscribeFuture.await(time, TimeUnit.MILLISECONDS)) {
if (!subscribeFuture.cancel(false)) {
subscribeFuture.onComplete((res, e) -> {
if (e == null) {
this.unsubscribe(subscribeFuture, threadId);
}
});
}
this.acquireFailed(threadId);
return false;
} else {
//在waitTime时间内收到了锁被释放的通知
try {
time -= System.currentTimeMillis() - current;
if (time <= 0L) {
this.acquireFailed(threadId);
boolean var20 = false;
return var20;
} else {
boolean var16;
//用循环模拟不断重试的过程(下面才是重试代码,同上面的逻辑)
do {
long currentTime = System.currentTimeMillis();
ttl = this.tryAcquire(leaseTime, unit, threadId);
if (ttl == null) {
var16 = true;
return var16;
}
time -= System.currentTimeMillis() - currentTime;
if (time <= 0L) {
this.acquireFailed(threadId);
var16 = false;
return var16;
}
currentTime = System.currentTimeMillis();
//锁距离被删除剩余的时间<当前线程还需等待时间
if (ttl >= 0L && ttl < time) {
//getLatch:返回值为信号量,这里是利用信号量机制尝试等待并获取别人释放的信号,且其有最大等待时间
((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(threadId);
var16 = false;
return var16;
}
} finally {
//取消订阅
this.unsubscribe(subscribeFuture, threadId);
}
}
}
}
}
2、看门狗机制源码分析
//看门狗机制的实现
private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {
//自己设置了锁超时时间,则不用看门狗机制
if (leaseTime != -1L) {
return this.tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
} else {
//未设置锁超时时间,则默认超时时间为30s,且利用看门狗机制开启定时任务,每隔10s更新有效器至最初值
RFuture<Long> ttlRemainingFuture = this.tryLockInnerAsync(this.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
//未来任务的onComplete函数:因为上面的方法为异步调用,所以此时的方法相当于等待异步调用返回后才进行执行
ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
//没抛异常且获取锁成功,则执行下面业务逻辑
if (e == null) {
if (ttlRemaining == null) {
//主要是利用该方法进行时间的更新
this.scheduleExpirationRenewal(threadId);
}
}
});
return ttlRemainingFuture;
}
}
其中的 scheduleExpirationRenewal()方式来更新锁超时时间
private void renewExpiration() {
//ExpirationEntry为一个类,该类中有一个map和一个timeOut定时任务
RedissonLock.ExpirationEntry ee = (RedissonLock.ExpirationEntry)EXPIRATION_RENEWAL_MAP.get(this.getEntryName());
if (ee != null) {
//开启一个定时任务
Timeout task = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
public void run(Timeout timeout) throws Exception {
RedissonLock.ExpirationEntry ent = (RedissonLock.ExpirationEntry)RedissonLock.EXPIRATION_RENEWAL_MAP.get(RedissonLock.this.getEntryName());
if (ent != null) {
Long threadId = ent.getFirstThreadId();
if (threadId != null) {
//底层也为Lua脚本,用于更新当前key的过期时间为30s,以此实现超时续约
RFuture<Boolean> future = RedissonLock.this.renewExpirationAsync(threadId);
future.onComplete((res, e) -> {
if (e != null) {
RedissonLock.log.error("Can't update lock " + RedissonLock.this.getName() + " expiration", e);
} else {
if (res) {
RedissonLock.this.renewExpiration();
}
}
});
}
}
}
}, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS);
//每隔this.internalLockLeaseTime / 3L则执行一次该定时任务。
//所以,只要将ExpirationEntry ee装入concurrentHashMap,则其定时任务每隔10s就会一直执行
ee.setTimeout(task);
}
}
3、对于看门狗机制,献上自己的一点理解
1)在没有设置超时时间的情况下,才会开启超时续约。若设置了锁超时时 间,则在锁超时后会被释放
2)为什么在没有设置锁超时时间的情况下,要设置看门狗机制不断更新锁 超时时间?
首先要明白,redission的锁机制底层就是使用的redis的setnx。若没有设置锁超时时间的话,也就是redis中的key没有设置超时时间,那该key只有用户显示del时,该key才会被删除,即该锁被释放。如果系统在未释放锁宕机了,其永远不会del该key,则该锁一直得不到释放,就会出问题。所以在设置锁的key时,系统默认给其一个过期时间,但用户是不想让他过期的,那redission的办法就是,让锁要被释放时,就更新他的有效期,使其不会释放,在用户看来该锁就是不会过期的(实际上是一直在更新有效期)。但遇到宕机时,其ttl不会再被更新,那么该锁就会超时释放,而不会等待显示调用del才释放,保证了安全性。