Redission實現(xiàn)分布式鎖lock()和tryLock()方法的區(qū)別小結(jié)
本文主要介紹了 Redission實現(xiàn)分布式鎖lock()和tryLock()方法的區(qū)別小結(jié),具體如下:
lock.lock(30, TimeUnit.SECONDS); // 嘗試獲取鎖30秒,如果獲取不到則放棄
//嘗試獲取鎖,等待5秒,持有鎖10秒鐘 boolean success = lock.tryLock(5, 10, TimeUnit.SECONDS);
Redisson 是一種基于 Redis 的分布式鎖框架,提供了 lock()
和 tryLock()
兩種獲取鎖的方法。
lock()
方法是阻塞獲取鎖的方式,如果當前鎖被其他線程持有,則當前線程會一直阻塞等待獲取鎖,直到獲取到鎖或者發(fā)生超時或中斷等情況才會結(jié)束等待。該方法獲取到鎖之后可以保證線程對共享資源的訪問是互斥的,適用于需要確保共享資源只能被一個線程訪問的場景。Redisson 的 lock()
方法支持可重入鎖和公平鎖等特性,可以更好地滿足多線程并發(fā)訪問的需求。
而 tryLock()
方法是一種非阻塞獲取鎖的方式,在嘗試獲取鎖時不會阻塞當前線程,而是立即返回獲取鎖的結(jié)果,如果獲取成功則返回 true,否則返回 false。Redisson 的 tryLock()
方法支持加鎖時間限制、等待時間限制以及可重入等特性,可以更好地控制獲取鎖的過程和等待時間,避免程序出現(xiàn)長時間無法響應(yīng)等問題。
因此,兩種獲取鎖的方式各有優(yōu)缺點,在實際應(yīng)用中需要根據(jù)具體場景和業(yè)務(wù)需求來選擇合適的方法,以確保程序的正確性和高效性。
直接看代碼例子lock.tryLock等待時間和持有時間都為0時。
public String RedissonLock1() { RLock lock = redissonClient.getLock("order_lock"); boolean success = true; try { System.out.println("獲取鎖前的時間:"+LocalDateTime.now()); // 嘗試獲取鎖,等待0秒,持有鎖0秒鐘 success = lock.tryLock(0, 0, TimeUnit.SECONDS); // lock.lock(0, TimeUnit.SECONDS); System.out.println("獲取鎖后的時間:"+LocalDateTime.now()); if (success) { System.out.println(Thread.currentThread().getName() + "獲取到鎖"+ LocalDateTime.now()); // 模擬業(yè)務(wù)處理耗時 // TimeUnit.SECONDS.sleep(3); // 模擬業(yè)務(wù)處理耗時 大于鎖過期,可能導(dǎo)致非自己持有的鎖被釋放。 TimeUnit.SECONDS.sleep(20); } else { System.out.println(Thread.currentThread().getName() + "未能獲取到鎖,已放棄嘗試"); } } catch (InterruptedException e) { e.printStackTrace(); } finally { // 判斷當前線程是否持有鎖 if (success && lock.isHeldByCurrentThread()) { //釋放當前鎖 lock.unlock(); System.out.println(Thread.currentThread().getName() + "釋放鎖"+ LocalDateTime.now()); } } return ""; }
可以看到立即拿鎖,拿不到立即就返回
lock()方法的持有鎖時間為0時
public String RedissonLock2() { RLock lock = redissonClient.getLock("order_lock"); boolean success = true; try { System.out.println("獲取鎖前的時間:"+LocalDateTime.now()); // 嘗試獲取鎖,等待5秒,持有鎖10秒鐘 // success = lock.tryLock(0, 0, TimeUnit.SECONDS); lock.lock(0, TimeUnit.SECONDS); System.out.println("獲取鎖后的時間:"+LocalDateTime.now()); // 模擬業(yè)務(wù)處理耗時 // TimeUnit.SECONDS.sleep(3); // 模擬業(yè)務(wù)處理耗時 大于鎖過期,可能導(dǎo)致非自己持有的鎖被釋放。 TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } finally { // 判斷當前線程是否持有鎖 if (success && lock.isHeldByCurrentThread()) { //釋放當前鎖 lock.unlock(); System.out.println(Thread.currentThread().getName() + "釋放鎖"+ LocalDateTime.now()); } } return ""; }
可以看到第二次獲取鎖時是阻塞的,等著業(yè)務(wù)處理了20s后,才獲取到鎖。
lock()
方法是阻塞獲取鎖的方式,而 tryLock()
方法是一種非阻塞獲取鎖的方式。
可以結(jié)合文章一起看 看懂Redisson分布式鎖源碼,其實并不難
看門狗續(xù)約時間默認是30秒
lock()
和 tryLock()都會用的
tryAcquireAsync(),看到區(qū)別,其實核心是leaseTime是否 > 0
所以是否使用看門狗不算是tryLock和lock()的區(qū)別,可以看下面這5種情況。
//走if (leaseTime > 0)邏輯 lock.tryLock(5, 10, TimeUnit.SECONDS); lock.lock(10, TimeUnit.SECONDS); //不走if (leaseTime > 0)邏輯 lock.tryLock(0, -1, TimeUnit.SECONDS); lock.lock(-1, TimeUnit.SECONDS); //lock()不帶參數(shù),默認leaseTime = -1 lock.lock();
leaseTime不大于0時,會執(zhí)行scheduleExpirationRenewal(threadId);
當oldEntry == null時,執(zhí)行renewExpiration(); 就是個定時器,即是看門狗核心邏輯了。
private void renewExpiration() { ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName()); if (ee == null) { return; } Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() { @Override public void run(Timeout timeout) throws Exception { ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName()); if (ent == null) { return; } Long threadId = ent.getFirstThreadId(); if (threadId == null) { return; } CompletionStage<Boolean> future = renewExpirationAsync(threadId); future.whenComplete((res, e) -> { if (e != null) { log.error("Can't update lock {} expiration", getRawName(), e); EXPIRATION_RENEWAL_MAP.remove(getEntryName()); return; } if (res) { // reschedule itself renewExpiration(); } else { cancelExpirationRenewal(null); } }); } }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS); ee.setTimeout(task); }
其中有下面紅圈這段邏輯,也就是看門狗之所以在三分之一的時候執(zhí)行的原因, 沒有自定義時
config.setLockWatchdogTimeout(10000); // 設(shè)置鎖的看門狗超時時間為10秒
默認就是30秒 除以3 也就是10秒時執(zhí)行。
再看幾段實現(xiàn)分布式鎖核心的LUA腳本
1.拿鎖LUA。
ChaGPT解釋此段LUA:
這是 Redisson 中 RedissonLock
類實現(xiàn)的 tryLockInnerAsync()
方法。該方法用于嘗試獲取鎖,它會先檢查鎖是否已經(jīng)被其他線程持有,如果沒有被持有,則將當前線程設(shè)置為鎖的持有者,并返回 null;否則返回當前線程需要等待的時間,以便稍后再次嘗試獲取鎖。
具體地說,Lua 腳本的邏輯是這樣的:
- 如果鎖不存在或者鎖已經(jīng)被當前線程持有(使用 hash 存儲鎖的持有者),則將當前線程作為鎖的持有者,并且設(shè)置鎖的過期時間,并返回 null。這里的代碼具體操作包括使用
HEXISTS
命令檢查鎖的持有情況,使用HINCRBY
命令來設(shè)置持有者和持有計數(shù)器并提高計數(shù)器的值,以及使用PEXPIRE
命令來設(shè)置鎖的過期時間。 - 如果鎖已經(jīng)被其他線程持有,則返回當前線程需要等待的時間,以便稍后再次嘗試獲取鎖。這里使用
PTTL
命令來查詢鎖的過期時間,并返回給調(diào)用方。
需要注意的是,因為 tryLockInnerAsync
方法的實現(xiàn)是原子的,所以它可以保證在分布式環(huán)境下的正確性。同時,它也支持重入鎖的功能,因為它保留了鎖的持有者和持有計數(shù)器的值。
2.看門狗續(xù)租LUA
ChaGPT解釋此段LUA:
這是 Redisson 中 RedissonLock
類實現(xiàn)的 renewExpirationAsync(long threadId)
方法。該方法用于續(xù)期鎖的過期時間,以避免鎖在使用期間失效。首先,該方法會檢查當前線程是否持有鎖(通過 hash 存儲鎖的持有者),如果當前線程不是鎖的持有者,則會直接返回 false 表示續(xù)期失敗。如果當前線程是鎖的持有者,則會將鎖的過期時間重置為 internalLockLeaseTime
毫秒,并返回 true 表示續(xù)期成功。
具體來說,Lua 腳本的邏輯是這樣的:
- 如果當前線程不是鎖的持有者,則直接返回 0(false)。
- 如果當前線程是鎖的持有者,則將鎖的過期時間重置為
internalLockLeaseTime
毫秒,并返回 1(true)。
3.解鎖LUA
這是 Redisson 中 RedissonLock
類實現(xiàn)的 unlockInnerAsync(long threadId)
方法。該方法用于釋放鎖,首先它會檢查當前線程是否持有鎖(通過 hash 存儲鎖的持有者),如果當前線程不是鎖的持有者,則直接返回 null;如果當前線程是鎖的持有者,則將鎖的計數(shù)器減一,如果減完之后計數(shù)器大于 0,則續(xù)期鎖的過期時間,并返回 false 表示未成功釋放鎖;否則則刪除該鎖,并發(fā)布一個消息通知其他競爭者可以獲取到該鎖了,最后返回 true 表示成功釋放鎖。
具體來說,Lua 腳本的邏輯是這樣的:
- 如果當前線程不是鎖的持有者,則直接返回 null。
- 如果當前線程是鎖的持有者,則使用
HINCRBY
命令將鎖的計數(shù)器減一,并獲取計數(shù)器的當前值。 - 如果鎖的計數(shù)器大于 0,則使用
PEXPIRE
命令續(xù)期鎖的過期時間,并返回 0(false)。 - 如果鎖的計數(shù)器已經(jīng)為 0,則使用
DEL
命令刪除該鎖,并使用PUBLISH
命令發(fā)布一個消息通知其他競爭者可以獲取到該鎖了,最后返回 1(true)。
4.預(yù)留了一個強制暴力解鎖的方法給用戶
//沒有釋放鎖,導(dǎo)致整個系統(tǒng)的性能下降甚至故障,必須強行停止這個鎖時 使用lock.forceUnlock();
這是 Redisson 中 RedissonLock
類實現(xiàn)的 forceUnlockAsync()
方法。該方法用于強制釋放鎖,取消鎖定的狀態(tài)。在方法內(nèi)部,它會調(diào)用 cancelExpirationRenewal(null)
方法來停止看門狗線程的續(xù)期任務(wù)。然后使用 evalWriteAsync
方法執(zhí)行 Lua 腳本,通過將鎖的名字和訂閱通道作為參數(shù)傳入,刪除鎖并返回結(jié)果,同時發(fā)布一條消息到訂閱通道上,表示這個鎖被釋放了。
具體地說,Lua 腳本的邏輯是這樣的:
- 使用
DEL
命令刪除鎖,如果返回值為 1,則說明鎖被成功刪除,否則說明當前線程并沒有持有這個鎖,直接返回 0。 - 如果鎖被成功刪除,使用
PUBLISH
命令發(fā)布一條消息到訂閱通道上,表示這個鎖被釋放了,并返回 1。
在所有操作鎖的地方使用LUA腳本,單線程,天然線程安全。
到此這篇關(guān)于Redission實現(xiàn)分布式鎖lock()和tryLock()方法的區(qū)別小結(jié)的文章就介紹到這了,更多相關(guān)Redission lock()和tryLock() 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringMVC集成redis配置的多種實現(xiàn)方法
這篇文章主要介紹了SpringMVC集成redis配置的多種實現(xiàn)方法,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03Redis創(chuàng)建并修改Lua 環(huán)境的實現(xiàn)方法
為了在Redis服務(wù)器中執(zhí)行Lua腳本, Redis在服務(wù)器內(nèi)嵌了一個Lua環(huán)境, 并對這個Lua環(huán)境進行了一系列修改,本文主要介紹了Redis創(chuàng)建并修改Lua 環(huán)境的實現(xiàn)方法,具有一定的參考價值,感興趣的可以了解一下2024-05-05基于Redis實現(xiàn)API接口訪問次數(shù)限制
日常開發(fā)中會有一個常見的需求,需要限制接口在單位時間內(nèi)的訪問次數(shù),比如說某個免費的接口限制單個IP一分鐘內(nèi)只能訪問5次,該怎么實現(xiàn)呢,本文小編給大家介紹了如何基于Redis實現(xiàn)API接口訪問次數(shù)限制,需要的朋友可以參考下2024-11-11