Redisson之lock()和tryLock()的区别
Redisson之lock()和tryLock()的区别和原理解析
在Redisson中 lock() 方法 与 tryLock() 方法是有区别的!
我们先来阐述两者的区别,再分析它们的源码。
lock() 与 tryLock() 的区别
(1)返回值: lock() 是没有返回值的;tryLock() 的返回值是 boolean。
(2)时机:lock() 一直等锁释放;tryLock() 获取到锁返回true,获取不到锁并直接返回false。
(3)tryLock() 是可以被打断的,被中断的;lock是不可以。
tryLock()
@Override
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
// 转成毫秒,后面都是以毫秒为单位
long time = unit.toMillis(waitTime);
// 当前时间
long current = System.currentTimeMillis();
// 线程ID-线程标识
long threadId = Thread.currentThread().getId();
// 尝试获取锁 tryAcquire() !!!
Long ttl = tryAcquire(waitTime, leaseTime, unit, threadId);
// 如果上面尝试获取锁返回的是null,表示成功;如果返回的是时间则表示失败。
if (ttl == null) {
return true;
}
// 剩余等待时间 = 最大等待时间 -(用现在时间 - 获取锁前的时间)
time -= System.currentTimeMillis() - current;
// 剩余等待时间 < 0 失败
if (time <= 0) {
acquireFailed(waitTime, unit, threadId);
return false;
}
// 再次获取当前时间
current = System.currentTimeMillis();
// 重试逻辑,但不是简单的直接重试!
// subscribe是订阅的意思
RFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);
// 如果在剩余等待时间内,收到了释放锁那边发过来的publish,则才会再次尝试获取锁
if (!subscribeFuture.await(time, TimeUnit.MILLISECONDS)) {
if (!subscribeFuture.cancel(false)) {
subscribeFuture.onComplete((res, e) -> {
if (e == null) {
// 取消订阅
unsubscribe(subscribeFuture, threadId);
}
});
}
// 获取锁失败
acquireFailed(waitTime, unit, threadId);
return false;
}
try {
// 又重新计算了一下,上述的等待时间
time -= System.currentTimeMillis() - current;
if (time <= 0) {
acquireFailed(waitTime, unit, threadId);
return false;
}
// 重试!
while (true) {
long currentTime = System.currentTimeMillis();
ttl = tryAcquire(waitTime, leaseTime, unit, threadId);
// 成功
if (ttl == null) {
return true;
}
// 又获取锁失败,再次计算上面的耗时
time -= System.currentTimeMillis() - currentTime;
if (time <= 0) {
acquireFailed(waitTime, unit, threadId);
return false;
}
currentTime = System.currentTimeMillis();
// 采用信号量的方式重试!
if (ttl >= 0 && ttl < time) {
subscribeFuture.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
} else {
subscribeFuture.getNow().getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
}
// 重新计算时间(充足就继续循环)
time -= System.currentTimeMillis() - currentTime;
if (time <= 0) {
acquireFailed(waitTime, unit, threadId);
return false;
}
}
} finally {
unsubscribe(subscribeFuture, threadId);
}
}
lock()
private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
// 获取当前线程 ID
long threadId = Thread.currentThread().getId();
// 获取锁,正常获取锁则ttl为null,竞争锁时返回锁的过期时间
Long ttl = tryAcquire(-1, leaseTime, unit, threadId);
if (ttl == null) {
return;
}
// 订阅锁释放事件
// 如果当前线程通过 Redis 的 channel 订阅锁的释放事件获取得知已经被释放,则会发消息通知待等待的线程进行竞争
RFuture<RedissonLockEntry> future = subscribe(threadId);
if (interruptibly) {
commandExecutor.syncSubscriptionInterrupted(future);
} else {
commandExecutor.syncSubscription(future);
}
try {
while (true) {
// 循环重试获取锁,直至重新获取锁成功才跳出循环
// 此种做法阻塞进程,一直处于等待锁手动释放或者超时才继续线程
ttl = tryAcquire(-1, leaseTime, unit, threadId);
if (ttl == null) {
break;
}
if (ttl >= 0) {
try {
future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
if (interruptibly) {
throw e;
}
future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
}
} else {
if (interruptibly) {
future.getNow().getLatch().acquire();
} else {
future.getNow().getLatch().acquireUninterruptibly();
}
}
}
} finally {
// 最后释放订阅事件
unsubscribe(future, threadId);
}
}
建议应尽量使用tryLock(),且携带参数,因为可设置最大等待时间以及可及时获取加锁返回值,后续可做一些其他加锁失败的业务。