共計 3553 個字符,預計需要花費 9 分鐘才能閱讀完成。
自動寫代碼機器人,免費開通
這篇文章主要介紹 Redis 如何實現可重入鎖的設計,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
但是仍然有些場景是不滿?的,例如? 個?法獲取到鎖之后,可能在?法內調這個?法此時就獲取不到鎖了。這個時候我們就需要把鎖改進成可 重?鎖了。重?鎖,指的是以線程為單位,當?個線程獲取對象鎖之后,這個線程可以再次獲取本對象上的鎖,?其 他的線程是不可以的。可重?鎖的意義在于防?死鎖。實現原理是通過為每個鎖關聯?個請求計數器和?個占有它的線程。當計數為 0 時,認為鎖是未被占有 的;線程請求?個未被占有的鎖時,JVM 將記錄鎖的占有者,并且將請求計數器置為 1。如果同?個線程再次請求這個鎖,計數將遞增;每次占?線程退出同步塊,計數器值將遞減。直到計數器 為 0, 鎖被釋放。關于?類和?類的鎖的重?:?類覆寫了?類的 synchonized ?法,然后調??類中的?法,此時如果沒有重?的鎖,那么這段代碼將產?死鎖。
代碼演示
不可重?
不可重?鎖
使用不可重入鎖
當前線程執? call() ?法?先獲取 lock,接下來執? inc() ?法就?法執? inc() 中的邏輯,必須先釋放鎖。該例很好的說明了不可重?鎖。
可重入鎖
鎖實現
鎖使用
可重?意味著線程可進?它已經擁有的鎖的同步代碼塊。
設計兩個線程調? call() ?法,第?個線程調? call() ?法獲取鎖,進? lock() ?法,由于初始 lockedBy 是 null,所以不會進? while ?掛起當前線程,?是增量 lockedCount 并記錄 lockBy 為第 ?個線程。
接著第?個線程進? inc() ?法,由于同?進程,所以不會進? while ?掛起,接著增量 lockedCount,當第?個線程嘗試 lock,由于 isLocked=true, 所以他不會獲取該鎖,直到第?個線程調?兩次 unlock() 將 lockCount 遞減為 0,才將標記為 isLocked 設置為 false。
設計思路
假設鎖的 key 為“lock”,hashKey 是當前線程的 id:“threadId”,鎖自動釋放時間假設為 20。
獲取鎖
判斷 lock 是否存在 EXISTS lock
不存在,則自己獲取鎖,記錄重入層數為 1.
存在,說明有人獲取鎖了,繼續判斷是不是自己的鎖,即判斷當前線程 id 作為 hashKey 是否存在:HEXISTS lock threadId
不存在,說明鎖已經有了,且不是自己獲取的,鎖獲取失敗.
存在,說明是自己獲取的鎖,重入次數 +1:HINCRBY lock threadId 1,最后更新鎖自動釋放時間,EXPIRE lock 20
釋放鎖
判斷當前線程 id 作為 hashKey 是否存在:HEXISTS lock threadId
不存在,說明鎖已失效
存在,說明鎖還在,重入次數減 1:HINCRBY lock threadId -1,
獲取新的重入次數,判斷重入次數是否為 0,為 0 說明鎖全部釋放,刪除 key:DEL lock
因此,存儲在鎖中的信息就必須包含:key、線程標識、重入次數。不能再使用簡單的 key-value 結構,這里推薦使用 hash 結構。而且要讓所有指令都在同一個線程中操作,那么使用 lua 腳本。
lua 腳本
lock.lua
local key = KEYS[1]; -- 第 1 個參數, 鎖的 keylocal threadId = ARGV[1]; -- 第 2 個參數, 線程唯一標識 local releaseTime = ARGV[2]; -- 第 3 個參數, 鎖的自動釋放時間 if(redis.call( exists , key) == 0) then -- 判斷鎖是否已存在
redis.call( hset , key, threadId, 1 -- 不存在, 則獲取鎖
redis.call(expire , key, releaseTime); -- 設置有效期
return 1; -- 返回結果 end;if(redis.call( hexists , key, threadId) == 1) then -- 鎖已經存在,判斷 threadId 是否是自己
redis.call( hincrby , key, threadId, 1 -- 如果是自己,則重入次數 +1
redis.call(expire , key, releaseTime); -- 設置有效期
return 1; -- 返回結果 end;return 0; -- 代碼走到這里, 說明獲取鎖的不是自己,獲取鎖失敗
unlock.lua
-- 鎖的 keylocal key = KEYS[1];-- 線程唯一標識 local threadId = ARGV[1];-- 判斷當前鎖是否還是被自己持有 if (redis.call( hexists , key, threadId) == 0) then-- 如果已經不是自己,則直接返回
return nil;end;-- 是自己的鎖,則重入次數減一 local count = redis.call(hincrby , key, threadId, -1);-- 判斷重入次數是否已為 0if (count == 0) then-- 等于 0,說明可以釋放鎖,直接刪除
redis.call(del , key);
return nil;end;
在項目中集成
編寫 RedisLock 類
@Getter@Setterpublic class RedisLock {
private RedisTemplate redisTemplate;
private DefaultRedisScript Long lockScript;
private DefaultRedisScript Object unlockScript;
public RedisLock(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
// 加載釋放鎖的腳本
this.lockScript = new DefaultRedisScript ();
this.lockScript.setScriptSource(new ResourceScriptSource(new ClassPathResource( lock.lua)));
this.lockScript.setResultType(Long.class);
// 加載釋放鎖的腳本
this.unlockScript = new DefaultRedisScript ();
this.unlockScript.setScriptSource(new ResourceScriptSource(new ClassPathResource( unlock.lua)));
}
/**
* 獲取鎖
* @param lockName 鎖名稱
* @param releaseTime 超時時間 (單位: 秒)
* @return key 解鎖標識
*/
public String tryLock(String lockName, long releaseTime) {
// 存入的線程信息的前綴,防止與其它 JVM 中線程信息沖突
String key = UUID.randomUUID().toString();
// 執行腳本
Long result = (Long)redisTemplate.execute(
lockScript,
Collections.singletonList(lockName),
key + Thread.currentThread().getId(), releaseTime);
// 判斷結果
if(result != null result.intValue() == 1) {
return key;
}else {
return null;
}
}
/**
* 釋放鎖
* @param lockName 鎖名稱
* @param key 解鎖標識
*/
public void unlock(String lockName, String key) {
// 執行腳本
redisTemplate.execute(
unlockScript,
Collections.singletonList(lockName),
key + Thread.currentThread().getId(), null);
}}
以上是“Redis 如何實現可重入鎖的設計”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注丸趣 TV 行業資訊頻道!
向 AI 問一下細節