即若當前執行緒執行某個方法已經獲取了該鎖,那麼在方法中嘗試再次獲取鎖時,就會獲取不到而阻塞。
可重入鎖,也叫做遞迴鎖,指的是在同一執行緒內,外層函數獲得鎖之後,內層遞迴函數仍然可以獲取到該鎖。 就是同一個執行緒再次進入同樣程式碼時,可以再次拿到該鎖。
防止在同一執行緒中多次獲取鎖而導致死鎖發生。
注:在java的程式設計中synchronized 和 ReentrantLock都是可重入鎖。
步驟1:雙重加鎖邏輯
public class SynchronizedDemo { //模擬庫存100 int count=100; public synchronized void operation(){ log.info("第一層鎖:減庫存"); //模擬減庫存 count--; add(); log.info("下訂單結束庫存剩餘:{}",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 - 下訂單結束庫存剩餘: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 - 下訂單結束庫存剩餘: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 - 下訂單結束庫存剩餘:97
ReentrantLock,是一個可重入且獨佔式的鎖,是一種遞迴無阻塞的同步鎖。和synchronized關鍵字相比,它更靈活、更強大,增加了輪詢、超時、中斷
等高階功能。
步驟1:雙重加鎖邏輯
public class ReentrantLockDemo { private Lock lock = new ReentrantLock(); public void doSomething(int n){ try{ //進入遞迴第一件事:加鎖 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次--------
setnx雖然可以實現分散式鎖,但是不可重入,在一些複雜的業務場景,我們需要分散式重入鎖時,
對於redis的重入鎖業界還是有很多解決方案的,目前最流行的就是採用Redisson
。【相關推薦:Redis視訊教學】
什麼是Redisson?
案例實戰:體驗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 :實現了java.util.concurrent.locks.Lock介面,同時還支援自動過期解鎖。 RLock lock = redissonClient.getLock(key); //步驟2:嘗試拿鎖 // 1. 預設的拿鎖 //lock.tryLock(); // 2. 支援過期解鎖功能,10秒鐘以後過期自動解鎖, 無需呼叫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 { // 業務程式碼 log.info("執行緒{}業務邏輯處理: {},遞迴{}" ,Thread.currentThread().getName(), key,n); //模擬處理業務 Thread.sleep(1000 * 5); //模擬進入遞迴 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(3, 10, TimeUnit.SECONDS);
區別:
最佳實戰:
lock.lock(10,TimeUnit.SECONDS); 省掉看門狗續期操作,自動解鎖時間一定要大於業務執行時間,手動解鎖
步驟3:測試
存取3次:http://127.0.0.1:9090/lock?key=ljw
執行緒http-nio-9090-exec-1業務邏輯處理: ljw,遞迴1 執行緒http-nio-9090-exec-2未取得鎖 執行緒http-nio-9090-exec-1業務邏輯處理: ljw,遞迴2 執行緒http-nio-9090-exec-3未取得鎖 執行緒http-nio-9090-exec-1業務邏輯處理: ljw,遞迴3 執行緒http-nio-9090-exec-1解鎖退出 執行緒http-nio-9090-exec-1解鎖退出 執行緒http-nio-9090-exec-1解鎖退出
通過測試結果:
上面介紹了分散式重入鎖的相關知識,證明了Redisson工具能實現了可重入鎖的功能。其實Redisson工具包中還包含了讀寫鎖(ReadWriteLock)和 紅鎖(RedLock)等相關功能,我們下篇文章再詳細研究。
更多程式設計相關知識,請存取:!!
以上就是什麼是可重入鎖?詳解redis實現分散式重入鎖的方法的詳細內容,更多請關注TW511.COM其它相關文章!