Redisson之lock()和tryLock()的區(qū)別及說明
lock()和tryLock()的區(qū)別和原理解析
在Redisson中 lock() 方法 與 tryLock() 方法是有區(qū)別的!
我們先來闡述兩者的區(qū)別,再分析它們的源碼。

lock() 與 tryLock() 的區(qū)別
(1)返回值: lock() 是沒有返回值的;tryLock() 的返回值是 boolean。
(2)時機(jī):lock() 一直等鎖釋放;tryLock() 獲取到鎖返回true,獲取不到鎖并直接返回false。
(3)tryLock() 是可以被打斷的,被中斷的;lock是不可以。
tryLock()
@Override
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
// 轉(zhuǎn)成毫秒,后面都是以毫秒為單位
long time = unit.toMillis(waitTime);
// 當(dāng)前時間
long current = System.currentTimeMillis();
// 線程ID-線程標(biāo)識
long threadId = Thread.currentThread().getId();
// 嘗試獲取鎖 tryAcquire() !!!
Long ttl = tryAcquire(waitTime, leaseTime, unit, threadId);
// 如果上面嘗試獲取鎖返回的是null,表示成功;如果返回的是時間則表示失敗。
if (ttl == null) {
return true;
}
// 剩余等待時間 = 最大等待時間 -(用現(xiàn)在時間 - 獲取鎖前的時間)
time -= System.currentTimeMillis() - current;
// 剩余等待時間 < 0 失敗
if (time <= 0) {
acquireFailed(waitTime, unit, threadId);
return false;
}
// 再次獲取當(dāng)前時間
current = System.currentTimeMillis();
// 重試邏輯,但不是簡單的直接重試!
// subscribe是訂閱的意思
RFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);
// 如果在剩余等待時間內(nèi),收到了釋放鎖那邊發(fā)過來的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);
}
// 重新計算時間(充足就繼續(xù)循環(huán))
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 {
// 獲取當(dāng)前線程 ID
long threadId = Thread.currentThread().getId();
// 獲取鎖,正常獲取鎖則ttl為null,競爭鎖時返回鎖的過期時間
Long ttl = tryAcquire(-1, leaseTime, unit, threadId);
if (ttl == null) {
return;
}
// 訂閱鎖釋放事件
// 如果當(dāng)前線程通過 Redis 的 channel 訂閱鎖的釋放事件獲取得知已經(jīng)被釋放,則會發(fā)消息通知待等待的線程進(jìn)行競爭
RFuture<RedissonLockEntry> future = subscribe(threadId);
if (interruptibly) {
commandExecutor.syncSubscriptionInterrupted(future);
} else {
commandExecutor.syncSubscription(future);
}
try {
while (true) {
// 循環(huán)重試獲取鎖,直至重新獲取鎖成功才跳出循環(huán)
// 此種做法阻塞進(jìn)程,一直處于等待鎖手動釋放或者超時才繼續(xù)線程
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);
}
}建議應(yīng)盡量使用tryLock(),且攜帶參數(shù),因為可設(shè)置最大等待時間以及可及時獲取加鎖返回值,后續(xù)可做一些其他加鎖失敗的業(yè)務(wù)。
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
redis的五大數(shù)據(jù)類型應(yīng)用場景分析
這篇文章主要介紹了redis的五大數(shù)據(jù)類型實現(xiàn)原理,本文給大家分享五大數(shù)據(jù)類型的應(yīng)用場景分析,需要的朋友可以參考下2021-08-08
Redis 2.8-4.0過期鍵優(yōu)化過程全紀(jì)錄
這篇文章主要給大家介紹了關(guān)于Redis 2.8-4.0過期鍵優(yōu)化的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用Redis具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04

