背景介紹
前段時間,在寫公司的一個項目的時候,用到了分佈式鎖,一個同事告訴我說,分佈式鎖解鎖在高併發的時候會報錯。
下面看下模擬代碼:
這裏鎖的時間是 5 秒,而業務執行的時間是 20 秒。這裏模擬的是鎖的時間少於業務執行的時間。
第二次執行的時候,就會報錯,如下:
java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: 660a38bb-c50b-4117-8ee2-67da7b4303c6 thread-id: 62
錯誤分析
通過這個錯誤信息,也知道該如何解決這個問題,我們只需要判斷是當前線程再去解鎖,就不會報錯的。
可是爲什麼會報錯呢?
所以,我們需要先去搞清楚具體的執行流程。
最開始,我以爲,如果被鎖住,運行到 ① 就會被返回,後面經過測試,實際上是會走到第 ② 步,嘗試獲取不到鎖,就會返回,在返回之前呢,會執行 finally
的代碼,因爲 redisson 對鎖有續租的功能,所以,這時候鎖還是鎖住的,解鎖就會報錯,也就是第 ③ 步。
實際上,我們的想法的,如果業務執行出錯,我們在 finally 進行解鎖,以防止程序死鎖。
顯然這樣寫代碼,不是我們所期望的,並且代碼也有問題。
優化代碼
@RequestMapping("/try-lock")
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();
}
}
}
以上,便是優化後的代碼,我們來一起分析一下。
分佈式加鎖主要分爲三步。
第一步,主要是獲取 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
,第一個參數 waitTime
,意思是等待 5 秒,如果還沒獲取到,就不再等待。第二個參數是 leaseTime
,意思是鎖的釋放時間。
第三步,就是我們業務代碼。
try {
TimeUnit.SECONDS.sleep(20);
return "success";
} catch (Exception e) {
return "business exception";
} finally {
if (rLock.isLocked() && rLock.isHeldByCurrentThread()) {
rLock.unlock();
}
}
在 finally 裏,我們做鎖的釋放操作,在釋放之前,我們對鎖的狀態和是否是當前線程做了判斷。
OK,如果你在實際的業務中如果遇到什麼問題,歡迎留言探討。
引申分析
- lock 和 tryLock 區別?
簡單來說,lock 會一直阻塞,而 tryLock 加鎖失敗,會返回 false。
- 如果鎖的時間少於業務的時間,會怎麼樣?
通過上面的分析,我們知道 tryLock 會加鎖失敗,而 lock,在鎖到釋放時間後,即便業務沒有執行完,也會繼續執行,並且不會報錯。
@RequestMapping("/lock")
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();
}
}
}
- 在解鎖的時候,不判斷鎖的狀態,會報錯嗎,反正都會解鎖?
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