Redis之Redisson的可重入鎖原理及解讀
一. 什么是可重入鎖?
假設(shè)你去網(wǎng)吧上網(wǎng),網(wǎng)吧里的每臺(tái)電腦(比如1號(hào)機(jī))就是“一把鎖”,開機(jī)密碼(或身份證刷卡)就是“拿鎖的權(quán)限”,你就是程序里的“線程”——只有你刷了卡、開了機(jī),才能用這臺(tái)電腦,別人不能用,這就是“鎖”的作用(保證同一時(shí)間,只有一個(gè)線程能操作資源)。
場景1(可重入,正常情況):你刷身份證開機(jī),用1號(hào)機(jī)(相當(dāng)于“線程拿到鎖”),玩了一會(huì)兒,想去廁所(相當(dāng)于程序里“一個(gè)方法調(diào)用另一個(gè)方法,兩個(gè)方法都需要同一把鎖”),你不用關(guān)機(jī)、釋放1號(hào)機(jī)(不用釋放鎖),直接起身去廁所,回來后還能直接坐回1號(hào)機(jī)、繼續(xù)玩——這就是“可重入”,怎么拿、怎么用,不卡殼,不用等自己“釋放”電腦,也不會(huì)卡死。
場景2(不可重入,卡死情況):如果這臺(tái)電腦是“不可重入”的,你開機(jī)用了1號(hào)機(jī)后,想去廁所,必須先關(guān)機(jī)、釋放1號(hào)機(jī)(釋放鎖),才能起身;等你從廁所回來,想再用1號(hào)機(jī),還得重新刷卡開機(jī)(重新拿鎖)。可你根本不想關(guān)機(jī)(業(yè)務(wù)還沒做完),一旦關(guān)機(jī),之前玩的內(nèi)容就沒了,不關(guān)機(jī)又沒法起身——這就是程序里的“死鎖”,程序直接卡死,沒法繼續(xù)運(yùn)行。
對(duì)應(yīng)到程序里,再簡單說一句:你寫了兩個(gè)方法A和B,兩個(gè)方法都需要同一把鎖(相當(dāng)于都需要用1號(hào)機(jī)),方法A里會(huì)調(diào)用方法B(相當(dāng)于用1號(hào)機(jī)時(shí),中途要做另一個(gè)需要用1號(hào)機(jī)的操作)。用Redisson可重入鎖,程序執(zhí)行A、再調(diào)用B時(shí),能直接執(zhí)行,不卡頓;用不可重入鎖,程序會(huì)卡在B那里,自己等自己釋放鎖,永遠(yuǎn)等不到,直接卡死。

二、怎么實(shí)現(xiàn)的“重入”?
可重入鎖主要用計(jì)數(shù)器實(shí)現(xiàn)的。計(jì)數(shù)器的唯一作用,就是記著你“拿了幾次鎖”,避免“釋放一次就把鎖徹底放走”,也避免自己卡死自己,全程由Redisson自動(dòng)管理,我們不用手動(dòng)操作。
具體工作流程:
- 你第一次刷身份證,開機(jī)用1號(hào)機(jī)(線程第一次獲取鎖),Redisson的計(jì)數(shù)器就記1(相當(dāng)于“你第一次占用1號(hào)機(jī)”);
- 你玩到中途,想重啟電腦(相當(dāng)于程序里“方法A調(diào)用方法B,再次獲取同一把鎖”),因?yàn)槭悄惚救耍ㄍ粋€(gè)線程),Redisson不用讓你重新刷卡,直接把計(jì)數(shù)器加1,變成2(相當(dāng)于“你第二次占用1號(hào)機(jī)”);
- 重啟完成,你繼續(xù)玩(相當(dāng)于methodB執(zhí)行完),釋放一次鎖,計(jì)數(shù)器就減1,變成1(相當(dāng)于“你釋放了一次占用,但還在使用1號(hào)機(jī)”);
- 你玩完了,關(guān)機(jī)離開(相當(dāng)于methodA執(zhí)行完),再釋放一次鎖,計(jì)數(shù)器減1,變成0(相當(dāng)于“你徹底釋放1號(hào)機(jī)”);
- 只有計(jì)數(shù)器變成0,這把鎖才真正放開,其他人才可以刷身份證、開機(jī)用1號(hào)機(jī)(其他線程才能獲取這把鎖)。
重點(diǎn)提醒:如果沒有計(jì)數(shù)器,會(huì)出什么問題?
就像你在網(wǎng)吧,重啟電腦(釋放一次鎖)后,計(jì)數(shù)器沒記錄,系統(tǒng)會(huì)誤以為你“徹底離開”,直接把1號(hào)機(jī)釋放,別人可能趁你還在座位上,就刷卡開機(jī)——對(duì)應(yīng)程序里,就是“鎖提前釋放”,多個(gè)線程同時(shí)操作一個(gè)資源(比如同時(shí)改一個(gè)數(shù)據(jù)),會(huì)導(dǎo)致數(shù)據(jù)錯(cuò)亂、程序出問題。
補(bǔ)充一句:
Redisson已經(jīng)幫我們把“計(jì)數(shù)器”“線程標(biāo)識(shí)”(識(shí)別是不是同一個(gè)線程拿鎖)全部封裝好了,我們不用自己寫一行代碼管理計(jì)數(shù)器,只需要調(diào)用簡單的lock()(拿鎖)和unlock()(釋放鎖),就能實(shí)現(xiàn)可重入,這也是Redisson比其他Redis客戶端好用的地方。
三、Redisson可重入鎖怎么用?
第一步:準(zhǔn)備環(huán)境
1. 給項(xiàng)目加依賴:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.23.3</version>
</dependency>2. 配置Redis(復(fù)制到application.yml里,只改2個(gè)地方:Redis地址、Redis密碼,沒有密碼就刪掉密碼那一行):
spring:
redis:
host: 127.0.0.1 # 改成你自己的Redis地址,本地開發(fā)就是127.0.0.1
port: 6379 # 默認(rèn)端口,一般不用改
password: 123456 # 改成你自己的Redis密碼,沒有就刪掉這一行
redisson:
singleServerConfig:
address: redis://127.0.0.1:6379 # 和上面的Redis地址、端口一致
password: 123456 # 和上面的Redis密碼一致,沒有就刪掉這一行第二步:寫核心代碼
我們寫一個(gè)服務(wù)類,里面有兩個(gè)方法(methodA和methodB),methodA調(diào)用methodB,兩個(gè)方法都用同一把鎖,模擬“重入”場景(對(duì)應(yīng)網(wǎng)吧里“開機(jī)后重啟電腦”的場景),代碼里的注釋都寫得很直白,一看就懂,直接復(fù)制粘貼到項(xiàng)目里就行:
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
// 這個(gè)注解不用管,加了就能用
@Service
public class LockDemo {
// 注入Redisson,不用管怎么來的,加了依賴就能自動(dòng)注入
@Autowired
private RedissonClient redissonClient;
// 鎖的名字,必須唯一,相當(dāng)于網(wǎng)吧1號(hào)機(jī)的“編號(hào)”,兩個(gè)方法必須用同一個(gè)名字
private static final String LOCK_NAME = "myLock";
// 方法A:需要拿鎖(相當(dāng)于網(wǎng)吧開機(jī)用1號(hào)機(jī))
public void methodA() {
// 1. 獲取可重入鎖(拿鎖,就是刷身份證拿1號(hào)機(jī)的使用權(quán))
RLock lock = redissonClient.getLock(LOCK_NAME);
try {
// 2. 加鎖(開機(jī)),不用管其他的,調(diào)用這個(gè)方法就好
lock.lock();
System.out.println("程序(線程)進(jìn)入methodA,拿到鎖了(相當(dāng)于開機(jī)用1號(hào)機(jī))");
// 3. 調(diào)用methodB,methodB也需要同一把鎖(相當(dāng)于開機(jī)后重啟電腦,再次占用1號(hào)機(jī))
methodB();
// 模擬業(yè)務(wù)操作(比如存數(shù)據(jù)、改數(shù)據(jù)),不用改,照搬就行
Thread.sleep(1000);
} catch (Exception e) {
// 出錯(cuò)了也不用管,這里只是防止程序崩潰
e.printStackTrace();
} finally {
// 4. 釋放鎖(關(guān)機(jī)),必須寫在這里,防止程序出錯(cuò)后鎖沒釋放
lock.unlock();
System.out.println("程序(線程)退出methodA,釋放鎖了(相當(dāng)于關(guān)機(jī)離開1號(hào)機(jī))");
}
}
// 方法B:也需要拿同一把鎖(相當(dāng)于重啟電腦,再次占用1號(hào)機(jī))
public void methodB() {
// 還是同一把鎖(LOCK_NAME和上面一樣,相當(dāng)于還是1號(hào)機(jī))
RLock lock = redissonClient.getLock(LOCK_NAME);
try {
// 加鎖(重啟電腦),因?yàn)槭峭粋€(gè)程序,直接重入,不用等
lock.lock();
System.out.println("程序(線程)進(jìn)入methodB,重入鎖成功(相當(dāng)于重啟電腦,繼續(xù)用1號(hào)機(jī))");
// 模擬業(yè)務(wù)操作,不用改
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 釋放鎖(重啟完成),計(jì)數(shù)器減1,不是徹底釋放
lock.unlock();
System.out.println("程序(線程)退出methodB,釋放重入鎖(相當(dāng)于重啟完成,繼續(xù)使用1號(hào)機(jī))");
}
}
}第三步:測試
寫一個(gè)簡單的測試接口,調(diào)用上面的methodA,看控制臺(tái)輸出,就能知道可重入鎖有沒有生效,代碼還是復(fù)制粘貼:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
// 測試接口,不用改
@RestController
public class TestLockController {
// 注入上面寫的LockDemo類
@Autowired
private LockDemo lockDemo;
// 訪問這個(gè)接口,就能測試可重入鎖
@GetMapping("/testLock")
public String testLock() {
// 調(diào)用methodA,會(huì)自動(dòng)調(diào)用methodB,模擬重入場景
lockDemo.methodA();
return "測試成功!去看控制臺(tái)輸出";
}
}測試結(jié)果
啟動(dòng)項(xiàng)目,訪問接口(http://localhost:8080/testLock),打開控制臺(tái),會(huì)看到下面的輸出(順序不能亂):
程序(線程)進(jìn)入methodA,拿到鎖了(相當(dāng)于開機(jī)用1號(hào)機(jī))
程序(線程)進(jìn)入methodB,重入鎖成功(相當(dāng)于重啟電腦,繼續(xù)用1號(hào)機(jī))
程序(線程)退出methodB,釋放重入鎖(相當(dāng)于重啟完成,繼續(xù)使用1號(hào)機(jī))
程序(線程)退出methodA,釋放鎖了(相當(dāng)于關(guān)機(jī)離開1號(hào)機(jī))?
解讀一下這個(gè)結(jié)果:
程序先進(jìn)入methodA、拿到鎖(開機(jī)用1號(hào)機(jī)),然后直接進(jìn)入methodB(重入成功,相當(dāng)于重啟電腦,不用重新刷卡),再依次釋放methodB、methodA的鎖(重啟完成→關(guān)機(jī)離開)——這就證明可重入鎖生效了,Redisson的計(jì)數(shù)器也在正常工作(拿2次、放2次,最后徹底釋放)。
四、注意事項(xiàng)
- 同一把鎖的名字必須一致:相當(dāng)于網(wǎng)吧里“1號(hào)機(jī)器”的鎖,你開機(jī)用了1號(hào)機(jī)(拿鎖),中途去廁所回來,還想坐1號(hào)機(jī)(重入鎖),必須找的是“1號(hào)機(jī)”的鎖,不能找“2號(hào)機(jī)”的鎖——對(duì)應(yīng)代碼里,methodA和methodB用的LOCK_NAME,必須都是“myLock”,不能一個(gè)寫“myLock”、一個(gè)寫“myLock1”,否則不是同一把鎖,沒法重入。
- lock()和unlock()要成對(duì)出現(xiàn):相當(dāng)于你去網(wǎng)吧,開機(jī)(lock()拿鎖)、關(guān)機(jī)(unlock()釋放鎖)要對(duì)應(yīng)。你開了一次機(jī),就必須關(guān)一次機(jī);開兩次機(jī)(比如開機(jī)后又重啟一次,相當(dāng)于重入),就必須關(guān)兩次機(jī)。多關(guān)一次(多unlock()),系統(tǒng)會(huì)報(bào)錯(cuò);少關(guān)一次(少unlock()),這臺(tái)機(jī)器會(huì)一直顯示“已占用”,別人沒法用(鎖泄漏)。
- unlock()必須寫在finally里:相當(dāng)于你在網(wǎng)吧上網(wǎng),不管是正常關(guān)機(jī)(業(yè)務(wù)正常執(zhí)行),還是突然停電、電腦死機(jī)(程序出錯(cuò)),機(jī)器都會(huì)自動(dòng)釋放(關(guān)機(jī)),不會(huì)一直占著位置。如果不寫在finally里,程序一出錯(cuò),就沒人“關(guān)機(jī)”,這臺(tái)機(jī)器永遠(yuǎn)被占用,別人用不了。
五、總結(jié)
可重入鎖:你在網(wǎng)吧坐1號(hào)機(jī)(拿鎖),中途去廁所、重啟電腦(重入),回來還能直接坐1號(hào)機(jī),不用等自己“釋放”機(jī)器,不卡頓;
Redisson核心原理:靠“計(jì)數(shù)器”記拿鎖次數(shù),拿一次加1,放一次減1,減到0才徹底釋放鎖,避免提前釋放和死鎖;
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Redis6 主從復(fù)制及哨兵機(jī)制的實(shí)現(xiàn)
本文主要介紹了Redis6 主從復(fù)制及哨兵機(jī)制的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07
Window server中安裝Redis的超詳細(xì)教程
這篇文章主要介紹了Window server中安裝Redis的教程,本文通過圖文實(shí)例代碼相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-11-11
redis保存AtomicInteger對(duì)象踩坑及解決
這篇文章主要介紹了redis保存AtomicInteger對(duì)象踩坑及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11
在CenOS系統(tǒng)下安裝和配置Redis數(shù)據(jù)庫的教程
這篇文章主要介紹了在CenOS系統(tǒng)下安裝和配置Redis數(shù)據(jù)庫的教程,Redis是一個(gè)可基于內(nèi)存的高性能NoSQL數(shù)據(jù)庫,需要的朋友可以參考下2015-11-11
Redis分布式鎖的超時(shí)機(jī)制實(shí)現(xiàn)的方法示例
本文介紹了Redis分布式鎖中超時(shí)機(jī)制的關(guān)鍵作用,通過設(shè)置鎖的過期時(shí)間防止客戶端崩潰導(dǎo)致的死鎖問題,具有一定的參考價(jià)值,感興趣的可以了解一下2025-10-10
Redis如何使用HyperLogLog的實(shí)現(xiàn)
本文主要介紹了Redis如何使用HyperLogLog的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06
Redis在windows環(huán)境下如何啟動(dòng)
這篇文章主要介紹了Redis在windows環(huán)境下如何啟動(dòng)的實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-04-04

