共計 6800 個字符,預(yù)計需要花費 17 分鐘才能閱讀完成。
這篇文章主要為大家分析了 redis 實現(xiàn)分布式重入鎖的方法是什么的相關(guān)知識點,內(nèi)容詳細(xì)易懂,操作細(xì)節(jié)合理,具有一定參考價值。如果感興趣的話,不妨跟著跟隨丸趣 TV 小編一起來看看,下面跟著丸趣 TV 小編一起深入學(xué)習(xí)“redis 實現(xiàn)分布式重入鎖的方法是什么”的知識吧。
什么是不可重入鎖?
即若當(dāng)前線程執(zhí)行某個方法已經(jīng)獲取了該鎖,那么在方法中嘗試再次獲取鎖時,就會獲取不到而阻塞。
什么是可重入鎖?
可重入鎖,也叫做遞歸鎖,指的是在同一線程內(nèi),外層函數(shù)獲得鎖之后,內(nèi)層遞歸函數(shù)仍然可以獲取到該鎖。
就是同一個線程再次進(jìn)入同樣代碼時,可以再次拿到該鎖。
可重入鎖作用?
防止在同一線程中多次獲取鎖而導(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:加個測試類
public static void main(String[] args) { SynchronizedDemo synchronizedDemo=new SynchronizedDemo();
for (int i = 0; i 3; i++) {
int finalI = i;
new Thread(()- { log.info( ------- 用戶 {} 開始下單 -------- , finalI);
synchronizedDemo.operation();
}).start();
}
}
步驟 3:測試
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)鍵字修飾的是方法,所有加鎖為實例對象:synchronizedDemo
運行結(jié)果可以看出減庫存和插入訂單都是每個線程都完整運行兩個方法完畢,才能釋放鎖,其他線程才能拿鎖,即是一個線程多次可以拿到同一個鎖,可重入。所以 synchronized 也是可重入鎖。
基于 ReentrantLock 的可重入鎖
ReentrantLock,是一個可重入且獨占式的鎖,是一種遞歸無阻塞的同步鎖。和 synchronized 關(guān)鍵字相比,它更靈活、更強大,增加了輪詢、超時、中斷等高級功能。
步驟 1:雙重加鎖邏輯
public class ReentrantLockDemo { private Lock lock = new ReentrantLock();
public void doSomething(int n){
try{
// 進(jìn)入遞歸第一件事:加鎖
lock.lock();
log.info(-------- 遞歸 {} 次 -------- ,n);
if(n =2){
try { Thread.sleep(1000*2);
} catch (InterruptedException e) { e.printStackTrace();
}
this.doSomething(++n);
}else{
return;
}
}finally { lock.unlock();
}
}
}
步驟 2:加個測試類
public static void main(String[] args) { ReentrantLockDemo reentrantLockDemo=new ReentrantLockDemo();
for (int i = 0; i 3; i++) {
int finalI = i;
new Thread(()- { log.info( ------- 用戶 {} 開始下單 -------- , finalI);
reentrantLockDemo.doSomething(1);
}).start();
}
}
步驟 3:測試
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 次 --------
運行結(jié)果可以看出,每個線程都可以多次加鎖解鎖的,ReentrantLock 是可重入的。
redis 如何實現(xiàn)分布式重入鎖?
setnx 雖然可以實現(xiàn)分布式鎖,但是不可重入,在一些復(fù)雜的業(yè)務(wù)場景,我們需要分布式重入鎖時,
對于 redis 的重入鎖業(yè)界還是有很多解決方案的,目前最流行的就是采用 Redisson。【相關(guān)推薦:Redis 視頻教程】
什么是 Redisson?
Redisson 是 Redis 官方推薦的 Java 版的 Redis 客戶端。
基于 Java 實用工具包中常用接口,為使用者提供了一系列具有分布式特性的常用工具類。
在網(wǎng)絡(luò)通信上是基于 NIO 的 Netty 框架,保證網(wǎng)絡(luò)通信的高性能。
在分布式鎖的功能上,它提供了一系列的分布式鎖;如:
可重入鎖(Reentrant Lock)
公平鎖(Fair Lock)
非公平鎖(unFair Lock)
讀寫鎖(ReadWriteLock)
聯(lián)鎖(MultiLock)
紅鎖(RedLock)
案例實戰(zhàn):體驗 redis 分布式重入鎖
步驟 1:Redisson 配置
Redisson 配置的可以查考:redis 分布式緩存(三十四)一一 SpringBoot 整合 Redission – 掘金 (juejin.cn)
https://juejin.cn/post/7057132897819426824
步驟 2:Redisson 重入鎖測試類
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:獲取一個分布式可重入鎖 RLock
// 分布式可重入鎖 RLock : 實現(xiàn)了 java.util.concurrent.locks.Lock 接口,同時還支持自動過期解鎖。 RLock lock = redissonClient.getLock(key);
// 步驟 2:嘗試拿鎖
// 1. 默認(rèn)的拿鎖
//lock.tryLock();
// 2. 支持過期解鎖功能,10 秒鐘以后過期自動解鎖, 無需調(diào)用 unlock 方法手動解鎖
//lock.tryLock(10, TimeUnit.SECONDS);
// 3. 嘗試加鎖,最多等待 3 秒,上鎖以后 10 秒后過期自動解鎖
// 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 三個加鎖動作:
lock.tryLock();
默認(rèn)的拿鎖
lock.tryLock(10, TimeUnit.SECONDS);
支持過期解鎖功能,10 秒鐘以后過期自動解鎖
lock.tryLock(3, 10, TimeUnit.SECONDS);
嘗試加鎖,最多等待 3 秒,上鎖以后 10 秒后過期自動解鎖
區(qū)別:
lock.lock():阻塞式等待。默認(rèn)加的鎖都是 30s
鎖的自動續(xù)期,如果業(yè)務(wù)超長,運行期間自動鎖上新的 30s。不用擔(dān)心業(yè)務(wù)時間長而導(dǎo)致鎖自動過期被刪掉(默認(rèn)續(xù)期)
加鎖的業(yè)務(wù)只要運行完成,就不會給當(dāng)前鎖續(xù)期,即使不手動解鎖,鎖默認(rèn)會在 30s 內(nèi)自動過期,不會產(chǎn)生死鎖問題
lock()如果我們未指定鎖的超時時間,就使用【看門狗默認(rèn)時間】:lockWatchdogTimeout = 30 * 1000
原理:只要占鎖成功,就會啟動一個定時任務(wù)【重新給鎖設(shè)置過期時間,新的過期時間就是看門狗的默認(rèn)時間】, 每隔 10 秒都會自動的再次續(xù)期,續(xù)成 30 秒
lock.lock(10,TimeUnit.SECONDS):10 秒鐘自動解鎖, 自動解鎖時間一定要大于業(yè)務(wù)執(zhí)行時間
出現(xiàn)的問題:在鎖時間到了以后,不會自動續(xù)期
原理:lock(10,TimeUnit.SECONDS)如果我們傳遞了鎖的超時時間,就發(fā)送給 redis 執(zhí)行腳本,進(jìn)行占鎖,默認(rèn)超時就是我們制定的時間
最佳實戰(zhàn):
lock.lock(10,TimeUnit.SECONDS); 省掉看門狗續(xù)期操作,自動解鎖時間一定要大于業(yè)務(wù)執(zhí)行時間,手動解鎖
步驟 3: 測試
訪問 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 解鎖退出
通過測試結(jié)果:
nio-9090-exec- 1 線程,在 getLock 方法遞歸了 3 次, 即證明了 lock.tryLock 是可重入鎖
只有當(dāng) nio-9090-exec- 1 線程執(zhí)行完后,io-9090-exec-2 nio-9090-exec-3 未取得鎖
因為 lock.tryLock(3, 10, TimeUnit.SECONDS),嘗試加鎖,最多等待 3 秒,上鎖以后 10 秒后過期自動解鎖
所以等了 3 秒都等不到,就放棄了
關(guān)于“redis 實現(xiàn)分布式重入鎖的方法是什么”就介紹到這了, 更多相關(guān)內(nèi)容可以搜索丸趣 TV 以前的文章,希望能夠幫助大家答疑解惑,請多多支持丸趣 TV 網(wǎng)站!