什么是可重入鎖?怎么實(shí)現(xiàn)重入鎖?下面本篇文章就來帶大家深入聊聊redis實(shí)現(xiàn)分布式重入鎖的方法,希望對(duì)大家有所幫助!
什么是不可重入鎖?
即若當(dāng)前線程執(zhí)行某個(gè)方法已經(jīng)獲取了該鎖,那么在方法中嘗試再次獲取鎖時(shí),就會(huì)獲取不到而阻塞。
什么是可重入鎖?
可重入鎖,也叫做遞歸鎖,指的是在同一線程內(nèi),外層函數(shù)獲得鎖之后,內(nèi)層遞歸函數(shù)仍然可以獲取到該鎖。 就是同一個(gè)線程再次進(jìn)入同樣代碼時(shí),可以再次拿到該鎖。
可重入鎖作用?
防止在同一線程中多次獲取鎖而導(dǎo)致死鎖發(fā)生。
注:在Java的編程中synchronized 和 ReentrantLock都是可重入鎖。
基于synchronized的可重入鎖
步驟1:雙重加鎖邏輯
public?class?SynchronizedDemo?{ ????//模擬庫存100 ????int?count=100; ????public?synchronized?void?operation(){ ????????log.info("第一層鎖:減庫存"); ????????//模擬減庫存 ????????count--; ????????add(); ????????log.info("下訂單結(jié)束庫存剩余:{}",count); ????} ????private?synchronized?void?add(){ ????????log.info("第二層鎖:插入訂單"); ????????try?{ ????????????Thread.sleep(1000*10); ????????}?catch?(InterruptedException?e)?{ ????????????e.printStackTrace(); ????????} ????} }
步驟2:加個(gè)測(cè)試類
public?static?void?main(String[]?args)?{ ????SynchronizedDemo?synchronizedDemo=new?SynchronizedDemo(); ????for?(int?i?=?0;?i?{ ????????????log.info("-------用戶{}開始下單--------",?finalI); ????????????synchronizedDemo.operation(); ????????}).start(); ????} }
步驟3:測(cè)試
20:44:04.013?[Thread-2]?INFO?com.agan.redis.controller.SynchronizedController?-?-------用戶2開始下單-------- 20:44:04.013?[Thread-1]?INFO?com.agan.redis.controller.SynchronizedController?-?-------用戶1開始下單-------- 20:44:04.013?[Thread-0]?INFO?com.agan.redis.controller.SynchronizedController?-?-------用戶0開始下單-------- 20:44:04.016?[Thread-2]?INFO?com.agan.redis.Reentrant.SynchronizedDemo?-?第一層鎖:減庫存 20:44:04.016?[Thread-2]?INFO?com.agan.redis.Reentrant.SynchronizedDemo?-?第二層鎖:插入訂單 20:44:14.017?[Thread-2]?INFO?com.agan.redis.Reentrant.SynchronizedDemo?-?下訂單結(jié)束庫存剩余:99 20:44:14.017?[Thread-0]?INFO?com.agan.redis.Reentrant.SynchronizedDemo?-?第一層鎖:減庫存 20:44:14.017?[Thread-0]?INFO?com.agan.redis.Reentrant.SynchronizedDemo?-?第二層鎖:插入訂單 20:44:24.017?[Thread-0]?INFO?com.agan.redis.Reentrant.SynchronizedDemo?-?下訂單結(jié)束庫存剩余:98 20:44:24.017?[Thread-1]?INFO?com.agan.redis.Reentrant.SynchronizedDemo?-?第一層鎖:減庫存 20:44:24.017?[Thread-1]?INFO?com.agan.redis.Reentrant.SynchronizedDemo?-?第二層鎖:插入訂單 20:44:34.017?[Thread-1]?INFO?com.agan.redis.Reentrant.SynchronizedDemo?-?下訂單結(jié)束庫存剩余:97
- 由于synchronized關(guān)鍵字修飾的是方法,所有加鎖為實(shí)例對(duì)象:synchronizedDemo
- 運(yùn)行結(jié)果可以看出減庫存和插入訂單都是每個(gè)線程都完整運(yùn)行兩個(gè)方法完畢,才能釋放鎖,其他線程才能拿鎖,即是一個(gè)線程多次可以拿到同一個(gè)鎖,可重入。所以synchronized也是可重入鎖。
基于ReentrantLock的可重入鎖
ReentrantLock,是一個(gè)可重入且獨(dú)占式的鎖,是一種遞歸無阻塞的同步鎖。和synchronized關(guān)鍵字相比,它更靈活、更強(qiáng)大,增加了輪詢、超時(shí)、中斷等高級(jí)功能。
步驟1:雙重加鎖邏輯
public?class?ReentrantLockDemo?{ ????private?Lock?lock?=??new?ReentrantLock(); ????public?void?doSomething(int?n){ ????????try{ ????????????//進(jìn)入遞歸第一件事:加鎖 ????????????lock.lock(); ????????????log.info("--------遞歸{}次--------",n); ????????????if(n<p><strong><span style="font-size: 18px;">步驟2:加個(gè)測(cè)試類</span></strong></p><pre class="brush:js;toolbar:false;">public?static?void?main(String[]?args)?{ ????ReentrantLockDemo?reentrantLockDemo=new?ReentrantLockDemo(); ????for?(int?i?=?0;?i?{ ????????????log.info("-------用戶{}開始下單--------",?finalI); ????????????reentrantLockDemo.doSomething(1); ????????}).start(); ????} }
步驟3:測(cè)試
20:55:23.533?[Thread-1]?INFO?com.agan.redis.controller.ReentrantController?-?-------用戶1開始下單-------- 20:55:23.533?[Thread-2]?INFO?com.agan.redis.controller.ReentrantController?-?-------用戶2開始下單-------- 20:55:23.533?[Thread-0]?INFO?com.agan.redis.controller.ReentrantController?-?-------用戶0開始下單-------- 20:55:23.536?[Thread-1]?INFO?com.agan.redis.Reentrant.ReentrantLockDemo?-?--------遞歸1次-------- 20:55:25.537?[Thread-1]?INFO?com.agan.redis.Reentrant.ReentrantLockDemo?-?--------遞歸2次-------- 20:55:27.538?[Thread-1]?INFO?com.agan.redis.Reentrant.ReentrantLockDemo?-?--------遞歸3次-------- 20:55:27.538?[Thread-2]?INFO?com.agan.redis.Reentrant.ReentrantLockDemo?-?--------遞歸1次-------- 20:55:29.538?[Thread-2]?INFO?com.agan.redis.Reentrant.ReentrantLockDemo?-?--------遞歸2次-------- 20:55:31.539?[Thread-2]?INFO?com.agan.redis.Reentrant.ReentrantLockDemo?-?--------遞歸3次-------- 20:55:31.539?[Thread-0]?INFO?com.agan.redis.Reentrant.ReentrantLockDemo?-?--------遞歸1次-------- 20:55:33.539?[Thread-0]?INFO?com.agan.redis.Reentrant.ReentrantLockDemo?-?--------遞歸2次-------- 20:55:35.540?[Thread-0]?INFO?com.agan.redis.Reentrant.ReentrantLockDemo?-?--------遞歸3次--------
- 運(yùn)行結(jié)果可以看出,每個(gè)線程都可以多次加鎖解鎖的,ReentrantLock是可重入的。
redis如何實(shí)現(xiàn)分布式重入鎖?
setnx雖然可以實(shí)現(xiàn)分布式鎖,但是不可重入,在一些復(fù)雜的業(yè)務(wù)場(chǎng)景,我們需要分布式重入鎖時(shí), 對(duì)于redis的重入鎖業(yè)界還是有很多解決方案的,目前最流行的就是采用Redisson。【相關(guān)推薦:Redis視頻教程】
什么是Redisson?
- Redisson是Redis官方推薦的Java版的Redis客戶端。
- 基于Java實(shí)用工具包中常用接口,為使用者提供了一系列具有分布式特性的常用工具類。
- 在網(wǎng)絡(luò)通信上是基于nio的Netty框架,保證網(wǎng)絡(luò)通信的高性能。
- 在分布式鎖的功能上,它提供了一系列的分布式鎖;如:
- 可重入鎖(Reentrant Lock)
- 公平鎖(Fair Lock)
- 非公平鎖(unFair Lock)
- 讀寫鎖(ReadWriteLock)
- 聯(lián)鎖(MultiLock)
- 紅鎖(RedLock)
案例實(shí)戰(zhàn):體驗(yàn)redis分布式重入鎖
步驟1:Redisson配置
Redisson配置的可以查考:redis分布式緩存(三十四)一一 SpringBoot整合Redission – 掘金 (juejin.cn)https://juejin.cn/post/7057132897819426824
步驟2:Redisson重入鎖測(cè)試類
public?class?RedisController?{ ????@Autowired ????RedissonClient?redissonClient; ????@GetMapping(value?=?"/lock") ????public?void?get(String?key)?throws?InterruptedException?{ ????????this.getLock(key,?1); ????} ????private?void?getLock(String?key,?int?n)?throws?InterruptedException?{ ????????//模擬遞歸,3次遞歸后退出 ????????if?(n?>?3)?{ ????????????return; ????????} ????????//步驟1:獲取一個(gè)分布式可重入鎖RLock ????????//分布式可重入鎖RLock?:實(shí)現(xiàn)了java.util.concurrent.locks.Lock接口,同時(shí)還支持自動(dòng)過期解鎖。 ????????RLock?lock?=?redissonClient.getLock(key); ????????//步驟2:嘗試拿鎖 ????????//?1.?默認(rèn)的拿鎖 ????????//lock.tryLock(); ????????//?2.?支持過期解鎖功能,10秒鐘以后過期自動(dòng)解鎖,?無需調(diào)用unlock方法手動(dòng)解鎖 ????????//lock.tryLock(10,?TimeUnit.SECONDS); ????????//?3.?嘗試加鎖,最多等待3秒,上鎖以后10秒后過期自動(dòng)解鎖 ????????//?lock.tryLock(3,?10,?TimeUnit.SECONDS); ????????boolean?bs?=?lock.tryLock(3,?10,?TimeUnit.SECONDS); ????????if?(bs)?{ ????????????try?{ ????????????????//?業(yè)務(wù)代碼 ????????????????log.info("線程{}業(yè)務(wù)邏輯處理:?{},遞歸{}"?,Thread.currentThread().getName(),?key,n); ????????????????//模擬處理業(yè)務(wù) ????????????????Thread.sleep(1000?*?5); ????????????????//模擬進(jìn)入遞歸 ????????????????this.getLock(key,?++n); ????????????}?catch?(Exception?e)?{ ????????????????log.error(e.getLocalizedMessage()); ????????????}?finally?{ ????????????????//步驟3:解鎖 ????????????????lock.unlock(); ????????????????log.info("線程{}解鎖退出",Thread.currentThread().getName()); ????????????} ????????}?else?{ ????????????log.info("線程{}未取得鎖",Thread.currentThread().getName()); ????????} ????} }
RLock三個(gè)加鎖動(dòng)作:
-
- 默認(rèn)的拿鎖
- lock.tryLock();
-
- 支持過期解鎖功能,10秒鐘以后過期自動(dòng)解鎖
- lock.tryLock(10, TimeUnit.SECONDS);
-
- 嘗試加鎖,最多等待3秒,上鎖以后10秒后過期自動(dòng)解鎖
-
lock.tryLock(3, 10, TimeUnit.SECONDS);
區(qū)別:
- lock.lock():阻塞式等待。默認(rèn)加的鎖都是30s
- 鎖的自動(dòng)續(xù)期,如果業(yè)務(wù)超長(zhǎng),運(yùn)行期間自動(dòng)鎖上新的30s。不用擔(dān)心業(yè)務(wù)時(shí)間長(zhǎng)而導(dǎo)致鎖自動(dòng)過期被刪掉(默認(rèn)續(xù)期)
- 加鎖的業(yè)務(wù)只要運(yùn)行完成,就不會(huì)給當(dāng)前鎖續(xù)期,即使不手動(dòng)解鎖,鎖默認(rèn)會(huì)在30s內(nèi)自動(dòng)過期,不會(huì)產(chǎn)生死鎖問題
- lock()如果我們未指定鎖的超時(shí)時(shí)間,就使用【看門狗默認(rèn)時(shí)間】: lockWatchdogTimeout = 30 * 1000
- 原理:只要占鎖成功,就會(huì)啟動(dòng)一個(gè)定時(shí)任務(wù)【重新給鎖設(shè)置過期時(shí)間,新的過期時(shí)間就是看門狗的默認(rèn)時(shí)間】,每隔10秒都會(huì)自動(dòng)的再次續(xù)期,續(xù)成30秒
- lock.lock(10,TimeUnit.SECONDS) :10秒鐘自動(dòng)解鎖,自動(dòng)解鎖時(shí)間一定要大于業(yè)務(wù)執(zhí)行時(shí)間
- 出現(xiàn)的問題:在鎖時(shí)間到了以后,不會(huì)自動(dòng)續(xù)期
- 原理:lock(10,TimeUnit.SECONDS)如果我們傳遞了鎖的超時(shí)時(shí)間,就發(fā)送給redis執(zhí)行腳本,進(jìn)行占鎖,默認(rèn)超時(shí)就是我們制定的時(shí)間
最佳實(shí)戰(zhàn):
-
lock.lock(10,TimeUnit.SECONDS); ?省掉看門狗續(xù)期操作,自動(dòng)解鎖時(shí)間一定要大于業(yè)務(wù)執(zhí)行時(shí)間,手動(dòng)解鎖
步驟3:測(cè)試
訪問3次:http://127.0.0.1:9090/lock?key=ljw
線程http-nio-9090-exec-1業(yè)務(wù)邏輯處理:?ljw,遞歸1 線程http-nio-9090-exec-2未取得鎖 線程http-nio-9090-exec-1業(yè)務(wù)邏輯處理:?ljw,遞歸2 線程http-nio-9090-exec-3未取得鎖 線程http-nio-9090-exec-1業(yè)務(wù)邏輯處理:?ljw,遞歸3 線程http-nio-9090-exec-1解鎖退出 線程http-nio-9090-exec-1解鎖退出 線程http-nio-9090-exec-1解鎖退出
通過測(cè)試結(jié)果:
- nio-9090-exec-1線程,在getLock方法遞歸了3次,即證明了lock.tryLock是可重入鎖
- 只有當(dāng)nio-9090-exec-1線程執(zhí)行完后,io-9090-exec-2 nio-9090-exec-3 未取得鎖 因?yàn)閘ock.tryLock(3, 10, TimeUnit.SECONDS),嘗試加鎖,最多等待3秒,上鎖以后10秒后過期自動(dòng)解鎖 所以等了3秒都等不到,就放棄了
總結(jié)
上面介紹了分布式重入鎖的相關(guān)知識(shí),證明了Redisson工具能實(shí)現(xiàn)了可重入鎖的功能。其實(shí)Redisson工具包中還包含了讀寫鎖(ReadWriteLock)和 紅鎖(RedLock)等相關(guān)功能,我們下篇文章再詳細(xì)研究。
更多編程相關(guān)知識(shí),請(qǐng)?jiān)L問:Redis視頻教程!!