Redisson 分布式鎖的正確使用
背景介紹
前段時間,在寫公司的一個項目的時候,用到了分布式鎖,一個同事告訴我說,分布式鎖解鎖在高并發(fā)的時候會報錯。
下面看下模擬代碼:

這里鎖的時間是 5 秒,而業(yè)務執(zhí)行的時間是 20 秒。這里模擬的是鎖的時間少于業(yè)務執(zhí)行的時間。
第二次執(zhí)行的時候,就會報錯,如下:
java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: 660a38bb-c50b-4117-8ee2-67da7b4303c6 thread-id: 62
錯誤分析
通過這個錯誤信息,也知道該如何解決這個問題,我們只需要判斷是當前線程再去解鎖,就不會報錯的。
可是為什么會報錯呢?
所以,我們需要先去搞清楚具體的執(zhí)行流程。

最開始,我以為,如果被鎖住,運行到 ① 就會被返回,后面經(jīng)過測試,實際上是會走到第 ② 步,嘗試獲取不到鎖,就會返回,在返回之前呢,會執(zhí)行 finally
的代碼,因為 redisson 對鎖有續(xù)租的功能,所以,這時候鎖還是鎖住的,解鎖就會報錯,也就是第 ③ 步。
實際上,我們的想法的,如果業(yè)務執(zhí)行出錯,我們在 finally 進行解鎖,以防止程序死鎖。
顯然這樣寫代碼,不是我們所期望的,并且代碼也有問題。
優(yōu)化代碼
public?String?tryLock()?{
????
????RLock?rLock?=?redissonClient.getLock("demo-spring-boot-redisson:try-lock");
????if?(Objects.isNull(rLock))?{
????????return?"lock?exception";
????}
????
????boolean?tryLock;
????try?{
????????tryLock?=?rLock.tryLock(3,?60,?TimeUnit.SECONDS);
????}?catch?(InterruptedException?e)?{
????????return?"get?lock?exception";
????}
????if?(!tryLock)?{
????????return?"get?lock?failed";
????}
????
????try?{
????????TimeUnit.SECONDS.sleep(20);
????????return?"success";
????}?catch?(Exception?e)?{
????????return?"business?exception";
????}?finally?{
????????if?(rLock.isLocked()?&&?rLock.isHeldByCurrentThread())?{
????????????rLock.unlock();
????????}
????}
}
以上,便是優(yōu)化后的代碼,我們來一起分析一下。
分布式加鎖主要分為三步。
第一步,主要是獲取 RLock
對象,并且我們對它做了判空。
RLock?rLock?=?redissonClient.getLock("demo-spring-boot-redisson:try-lock");
if?(Objects.isNull(rLock))?{
????return?"lock?exception";
}
第二步,嘗試加鎖,加鎖失敗,返回加鎖失敗。
boolean?tryLock;
try?{
????tryLock?=?rLock.tryLock(3,?60,?TimeUnit.SECONDS);
}?catch?(InterruptedException?e)?{
????return?"get?lock?exception";
}
if?(!tryLock)?{
????return?"get?lock?failed";
}
這里我們用的是 tryLock
,第一個參數(shù) waitTime
,意思是等待 5 秒,如果還沒獲取到,就不再等待。第二個參數(shù)是 leaseTime
,意思是鎖的釋放時間。
第三步,就是我們業(yè)務代碼。
try?{
????TimeUnit.SECONDS.sleep(20);
????return?"success";
}?catch?(Exception?e)?{
????return?"business?exception";
}?finally?{
????if?(rLock.isLocked()?&&?rLock.isHeldByCurrentThread())?{
????????rLock.unlock();
????}
}
在 finally 里,我們做鎖的釋放操作,在釋放之前,我們對鎖的狀態(tài)和是否是當前線程做了判斷。
OK,如果你在實際的業(yè)務中如果遇到什么問題,歡迎留言探討。
引申分析
lock 和 tryLock 區(qū)別?
簡單來說,lock 會一直阻塞,而 tryLock 加鎖失敗,會返回 false。
如果鎖的時間少于業(yè)務的時間,會怎么樣?
通過上面的分析,我們知道 tryLock 會加鎖失敗,而 lock,在鎖到釋放時間后,即便業(yè)務沒有執(zhí)行完,也會繼續(xù)執(zhí)行,并且不會報錯。
public?String?lock()?{
????RLock?rLock?=?redissonClient.getLock("demo-spring-boot-redisson:lock");
????if?(Objects.isNull(rLock))?{
????????return?"exception";
????}
????try?{
????????rLock.lock(5,?TimeUnit.SECONDS);
????????System.out.println("execute?business");
????????TimeUnit.SECONDS.sleep(20);
????????return?"success";
????}?catch?(Exception?e)?{
????????return?"lock?exception";
????}?finally?{
????????if?(rLock.isLocked())?{
????????????rLock.unlock();
????????}
????}
}
在解鎖的時候,不判斷鎖的狀態(tài),會報錯嗎,反正都會解鎖?
tryLock 不會。
而 lock 會報錯,報錯信息如下:
java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: 8b7b0374-506b-442f-9bc4-9e1c1cbf4d46 thread-id: 61