redis分布式锁演示代码:
public String hello() throws InterruptedException{
//获取一把锁,名称相同,就是同一把锁
RLock lock = redisson.getLock("my-lock");
//lock.lock();
lock.lock(10, TimeUnit.SECONDS);//自动解锁时间,一定要大于业务的执行时间
//问题lock.lock(10, TimeUnit.SECONDS)在锁时间到了之后,不会自动续期
try {
System.out.println("加锁成功!");
Thread.sleep(3000);
}finally {
lock.unlock();
}
return "hello";
}
lock.lock();
此为一个阻塞式锁,默认锁的过期时间为30s,如果30s内业务代码还没有执行完,将在1/3看门狗时间后自动续期;lock.lock(10, TimeUnit.SECONDS);
当我们使用此方法指定过期时间后,便又引发了一个问题, 当业务逻辑执行时间超过10s时,锁已经释放,此时如果还有其他线程访问,又可以得到锁; 且如上述代码中: try {
System.out.println("加锁成功!");
Thread.sleep(3000);
}finally {
lock.unlock();
}
当业务逻辑在30s执行完成后,他删的锁是下一个线程的锁,而不是它自己的锁;此时便会报出如下错误:
2,接下来,我们看一下他的源代码:
可以看到,当我们指定时间后,他会用我们指定的时间再次执行lock方法;
首先他会获取线程Id, 接着如果获取到锁,就直接返回,没有获取到就循环等待, 直至获取到锁;
我们再来看看tryAcquire方法:
他又调用了get方法, get方法里面又调用了tryAcquireAsync方法;
private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {
if (leaseTime != -1) {
return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
}
RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
if (e != null) {
return;
}
// lock acquired
if (ttlRemaining == null) {
scheduleExpirationRenewal(threadId);
}
});
return ttlRemainingFuture;
}
leaseTime则是我们指定的10s, 如果没有传时间,他会调用如下lock方法,将时间设置为-1;
调用tryLockInnerAsync函数
此方法是向redis发送一个lua脚本去占位执行,
private RFuture<Boolean> tryAcquireOnceAsync(long leaseTime, TimeUnit unit, long threadId) {
if (leaseTime != -1) {
return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
}
RFuture<Boolean> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
if (e != null) {
return;
}
// lock acquired
if (ttlRemaining) {
scheduleExpirationRenewal(threadId);
}
});
return ttlRemainingFuture;
}
它便会获取锁的看门狗时间getLockWatchdogTimeout()
可以看到,看门狗默认时间为30s; 如果占位成功! 就会继续监听
有异常直接返回, 没有异常根据代码,我们可以看到他将重新获取过期时间,
在renewExpiration方法中:
定时任务中的renewExpirationAsync()方法:
又是向redis发送lua脚本执行; 且它的internalLockLeaseTime又是看门狗时间,如下:
续期时间:
结论:
推荐使用传时间的lock方法,过期时间不要毫秒级别即可!
lock.lock(10, TimeUnit.SECONDS);//省掉续期时间