最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

揭秘springboot中Redisson?可重入鎖的實現(xiàn)原理

 更新時間:2026年02月10日 09:07:19   作者:給我點時間看書  
本文探究基于?Redisson?的可重入鎖原理,通過使用?hash?數(shù)據(jù)結(jié)構(gòu)?+?Lua?腳本實現(xiàn)可重入的分布式鎖,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

本文探究基于 Redisson 的可重入鎖原理,通過 RedissonLock 類中的源碼,學(xué)習(xí)如何使用 hash 數(shù)據(jù)結(jié)構(gòu) + Lua 腳本實現(xiàn)可重入的分布式鎖。

可重入鎖的業(yè)務(wù)場景

RLock lock = redissonClient.getLock("lock:business:");

void method1() {
    boolean isLock = lock.tryLock();
    if (!isLock) {
        log.error("方法 1 獲取鎖失敗");
    }
    try {
        log.info("方法 1 獲取鎖成功");
        method2();
    } finally {
        log.info("方法 1 釋放鎖鎖");
        lock.unlock();
    }
}

void method2() {
    boolean isLock = lock.tryLock();
    if (!isLock) {
        log.error("方法 2 獲取鎖失敗");
    }
    try {
        log.info("方法 2 獲取鎖成功");
    } finally {
        log.info("方法 2 釋放鎖鎖");
        lock.unlock();
    }
}

上面業(yè)務(wù)代碼中,方法 1 獲取鎖之后,需要調(diào)用調(diào)用方法 2,方法 2 同樣也需要獲取鎖保證線程安全,此時就需要重復(fù)獲取同一個鎖,這個鎖就叫做可重入鎖。

可重入鎖的實現(xiàn)邏輯

分為獲取鎖和釋放鎖兩部分來寫

獲取鎖

  1. 鎖存在,要判斷鎖標(biāo)識是否是自己的線程,不是自己的線程,那就說明有別的線程已經(jīng)獲取到鎖了,當(dāng)前線程獲取鎖失??;如果是自己的線程標(biāo)識,那就要給鎖計數(shù)加一(記錄獲取鎖的次數(shù))并重置有效期;
  2. 鎖不存在,就獲取鎖,添加當(dāng)前線程的標(biāo)識,獲取鎖次數(shù)加一,并設(shè)置有效期

釋放鎖

  1. 線程標(biāo)識不是自己,鎖可能過期釋放了,返回釋放失敗
  2. 是自己,鎖的計數(shù)減一,判斷鎖計數(shù)是否為 0,如果不為 0,重置鎖有效期,執(zhí)行下一段業(yè)務(wù),如果鎖計數(shù)為 0 了,就釋放鎖,此時流程結(jié)束。

源碼分析

本文源碼基于 JDK 17 + Redisson 3.39.0,只做可重入鎖部分的源碼探究,其余部分讀者可自行深入。

依舊是分為獲取鎖和釋放鎖兩部分探究,Redisson 底層使用 hash 數(shù)據(jù)結(jié)構(gòu) + Lua 腳本實現(xiàn)可重入的分布式鎖。

在看源碼之前,先看看 Redisson 源碼包中與本文相關(guān)的類和接口的實現(xiàn)與繼承關(guān)系,方便理解文章:

獲取鎖源碼

在業(yè)務(wù)中我們使用 redissonClient 獲取鎖,從tryLock()方法開啟 redisson 的可重入鎖源碼探索之旅。

// 注入 redissonClient 依賴
@Resource
private RedissonClient redissonClient;


// 業(yè)務(wù)中獲取鎖
RLock lock = redissonClient.getLock("lock:business");
boolean isLock = lock.tryLock();

tryLock()java.util.concurrent.locks包下的Lock接口中的方法,Redisson 對于其實現(xiàn)類有四個:

  1. RedissonLock 類
  2. RedissonFasterMultiLock 類
  3. RedissonMultiLock 類
  4. RedissonSpinLock 類

現(xiàn)在我們再深入看看 RedissonLock 類中的實現(xiàn)(如對其余實現(xiàn)類感興趣,讀者可自行閱讀源碼):

@Override
public boolean tryLock() {
    return get(tryLockAsync());
}

tryLock()方法是等待tryLockAsync()這個獲取鎖的異步操作完成,tryLockAsync()方法是org.redisson.RedissonBaseLock類中的一個方法,實現(xiàn)了org.redisson.api.RLockAsync接口的抽象方法。

@Override
public RFuture<Boolean> tryLockAsync() {
    return tryLockAsync(Thread.currentThread().getId());
}

tryLockAsync()方法則是調(diào)用org.redisson.api.RLockAsync接口中的重載方法,傳遞一個當(dāng)前線程的 ID,接下來再看看 RedissonLock 這個類中,這個重載方法的具體的實現(xiàn):

@Override
public RFuture<Boolean> tryLockAsync(long threadId) {
    return getServiceManager().execute(() -> tryAcquireOnceAsync(-1, -1, null, threadId));
}

該方法內(nèi)部調(diào)用了getServiceManager().execute(),異步執(zhí)行tryAcquireOnceAsync()方法。

private RFuture<Boolean> tryAcquireOnceAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
    CompletionStage<Boolean> acquiredFuture;
    if (leaseTime > 0) {
        acquiredFuture = tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
    } else {
        acquiredFuture = tryLockInnerAsync(waitTime, internalLockLeaseTime,
                TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
    }

 // ......其余代碼
    
}

可以看到在業(yè)務(wù)中如果使用無參的lock.tryLock()方法獲取鎖,那么給tryLockInnerAsync()方法傳遞的五個參數(shù):

  1. 等待時間 waitTime 設(shè)置為 -1
  2. 超時時間默認(rèn) 30 * 1000(RedissonLock 實例化的時候指定)
  3. 時間單位默認(rèn)毫秒
  4. 第四個參數(shù)當(dāng)前線程 ID
  5. 最后一個參數(shù)用來執(zhí)行 Lua 腳本,并返回一個布爾值

接下來就是獲取鎖的核心代碼:

<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
    return evalWriteSyncedNoRetryAsync(getRawName(), LongCodec.INSTANCE, command,
            "if ((redis.call('exists', KEYS[1]) == 0) " +
                        "or (redis.call('hexists', KEYS[1], ARGV[2]) == 1)) then " +
                    "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
                    "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                    "return nil; " +
                "end; " +
                "return redis.call('pttl', KEYS[1]);",
            Collections.singletonList(getRawName()), unit.toMillis(leaseTime), getLockName(threadId));
}

可以看到,Redisson 底層還是使用了 Lua 腳本來實現(xiàn)可重入鎖,我們忽略其他代碼,直接看 Lua 腳本做了什么事情:

參數(shù)解釋:

  1. KEYS[1]:鎖的 key
  2. ARGV[1]:過期時間
  3. ARGV[2]:線程 ID

可重入鎖使用 Redis 的 hash 結(jié)構(gòu)存儲值,file 是線程 ID,value 是獲取鎖的次數(shù)(value 是 file 對應(yīng)的值):

if ((redis.call('exists', KEYS[1]) == 0) 
    or (redis.call('hexists', KEYS[1], ARGV[2]) == 1)) then 
    redis.call('hincrby', KEYS[1], ARGV[2], 1); 
    redis.call('pexpire', KEYS[1], ARGV[1]); 
    return nil; 
end; 
return redis.call('pttl', KEYS[1]);
  1. 如果鎖不存在或者鎖存在且線程 ID 和當(dāng)前線程的 ID 一致,將獲取鎖的次數(shù)自增 1,且重置過期時間,之后返回操作成功;
  2. 如果以上 if 條件不成立,返回鎖的過期時間,鎖不存在就會返回 -2。

釋放鎖源碼

try {
    log.info("執(zhí)行具體業(yè)務(wù)");
} finally {
    lock.unlock();
}

通過獲取鎖的源碼分析,我們發(fā)現(xiàn)其實真正的核心代碼就是那段 Lua 腳本,在此我們也不做過多的其余代碼分析,直接來看核心代碼:

protected RFuture<Boolean> unlockInnerAsync(long threadId, String requestId, int timeout) {
    return evalWriteSyncedNoRetryAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                          "local val = redis.call('get', KEYS[3]); " +
                                "if val ~= false then " +
                                    "return tonumber(val);" +
                                "end; " +

                                "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
                                    "return nil;" +
                                "end; " +
                                "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
                                "if (counter > 0) then " +
                                    "redis.call('pexpire', KEYS[1], ARGV[2]); " +
                                    "redis.call('set', KEYS[3], 0, 'px', ARGV[5]); " +
                                    "return 0; " +
                                "else " +
                                    "redis.call('del', KEYS[1]); " +
                                    "redis.call(ARGV[4], KEYS[2], ARGV[1]); " +
                                    "redis.call('set', KEYS[3], 1, 'px', ARGV[5]); " +
                                    "return 1; " +
                                "end; ",
                            Arrays.asList(getRawName(), getChannelName(), getUnlockLatchName(requestId)),
                            LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime,
                            getLockName(threadId), getSubscribeService().getPublishCommand(), timeout);
}

其中 Redisson 底層釋放鎖源碼依舊使用 Lua 腳本,接下來具體看看腳本都干了什么:
參數(shù)解釋:

  1. KEYS[1]:鎖對象的 key
  2. KEYS[2]:頻道 key,發(fā)布所釋放的消息
  3. KEYS[3]:閂鎖 key,協(xié)調(diào)多個客戶端同時解鎖的情況
  4. ARGV[1]:發(fā)布的消息內(nèi)容
  5. ARGV[2]:鎖 key 的過期時間
  6. ARGV[3]:線程標(biāo)識
  7. ARGV[4]:發(fā)布命令
  8. ARGV[5]:閂鎖 key 過期時間
-- 第一段
local val = redis.call('get', KEYS[3]);
if val ~= false then 
    return tonumber(val);
end;

-- 第二段
if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then 
    return nil;
end;

-- 第三段
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1);
if (counter > 0) then 
    redis.call('pexpire', KEYS[1], ARGV[2]); 
    redis.call('set', KEYS[3], 0, 'px', ARGV[5]);
    return 0; 
else 
    redis.call('del', KEYS[1]); 
    redis.call(ARGV[4], KEYS[2], ARGV[1]); 
    redis.call('set', KEYS[3], 1, 'px', ARGV[5]);
    return 1; 
end;

第一段代碼是用來協(xié)調(diào)多個客戶端同時解鎖的請求,在本文過多涉及;

第二段代碼,通過鎖和線程標(biāo)識判斷是否是當(dāng)前線程獲取的鎖,如果不是,返回解鎖失?。?/p>

第三段代碼,就是釋放鎖的核心代碼了,執(zhí)行到這里,說明鎖存在且是當(dāng)前線程持有的:

  1. 先把 獲取鎖的次數(shù) 減 1,返回更新后的 獲取鎖次數(shù) 。
  2. 如果次數(shù)大于 0,說明還有業(yè)務(wù)代碼重入獲取鎖,此時重置鎖的過期時間;并將閂鎖 key 的值設(shè)置為 0,給一個過期時間確保其他線程在同一時刻嘗試進(jìn)行解鎖操作;
  3. 如果次數(shù)小于等于 0,那就說明業(yè)務(wù)已經(jīng)執(zhí)行完畢,刪除這個鎖;發(fā)布消息同之其它可能等待此鎖的客戶端這個鎖已經(jīng)被釋放;并將閂鎖 key 的值設(shè)置為 1,給定過期時間。

總結(jié)

  1. 獲取鎖:每次重入,都會計數(shù)自增一;
  2. 釋放鎖:每次釋放,都會計數(shù)減一,直到為 0,此時真正釋放鎖。

為什么要使用 Lua 腳本呢?

  1. Lua 腳本在 Redis 中是原子執(zhí)行的,分布式環(huán)境中,不會被其它線程影響,從而保持操作的原子性和數(shù)據(jù)的一致性;
  2. 通過將復(fù)雜的邏輯封裝到 Lua 腳本中,一次性執(zhí)行,減少網(wǎng)絡(luò)開銷。

到此這篇關(guān)于揭秘springboot中Redisson 可重入鎖的實現(xiàn)原理的文章就介紹到這了,更多相關(guān)Redisson 可重入鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 詳解Java?Unsafe如何花式操作內(nèi)存

    詳解Java?Unsafe如何花式操作內(nèi)存

    C++可以動態(tài)的分類內(nèi)存,而java并不能這樣,是不是java就不能操作內(nèi)存呢,其實是有其他辦法可以操作內(nèi)存的,下面就一起看看Unsafe是如何花式操作內(nèi)存的吧
    2023-08-08
  • idea 離線安裝lombok插件的方法步驟(圖文)

    idea 離線安裝lombok插件的方法步驟(圖文)

    這篇文章主要介紹了idea 離線安裝lombok插件的方法步驟(圖文),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-01-01
  • MyBatis多表操作查詢功能

    MyBatis多表操作查詢功能

    這篇文章主要介紹了MyBatis多表操作,包括一對一查詢,一對多查詢的模型,多對多查詢的需求,本文通過實例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2021-11-11
  • spring boot配置ssl(多cer格式)超詳細(xì)教程

    spring boot配置ssl(多cer格式)超詳細(xì)教程

    這篇文章主要介紹了spring boot配置ssl(多cer格式)超詳細(xì)教程,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2023-11-11
  • 基于Bigdecimal科學(xué)計數(shù)問題

    基于Bigdecimal科學(xué)計數(shù)問題

    這篇文章主要介紹了基于Bigdecimal科學(xué)計數(shù)問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-06-06
  • elasticsearch開發(fā)中data-streams使用解析

    elasticsearch開發(fā)中data-streams使用解析

    這篇文章主要為大家介紹了elasticsearch開發(fā)中data-streams使用解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-08-08
  • Struts2開發(fā) 基本配置與類型轉(zhuǎn)換

    Struts2開發(fā) 基本配置與類型轉(zhuǎn)換

    本篇文章,小編將為大家介紹關(guān)于Struts2開發(fā) 基本配置與類型轉(zhuǎn)換,有需要的朋友可以參考一下
    2013-04-04
  • Java線程的start方法回調(diào)run方法的操作技巧

    Java線程的start方法回調(diào)run方法的操作技巧

    面試過程中經(jīng)常會被面試官問到為什么我們調(diào)用start()方法時會執(zhí)行run()方法,為什么不能直接調(diào)用run()方法,問的一頭霧水,今天小編給大家介紹下Java線程的start方法回調(diào)run方法的操作技巧,需要的朋友參考下吧
    2017-11-11
  • 已解決:No ''Access-Control-Allow-Origin''跨域問題

    已解決:No ''Access-Control-Allow-Origin''跨域問題

    這篇文章主要介紹了已解決:No 'Access-Control-Allow-Origin' 跨域,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-06-06
  • Java 生成隨機字符串?dāng)?shù)組的實例詳解

    Java 生成隨機字符串?dāng)?shù)組的實例詳解

    這篇文章主要介紹了Java 生成隨機字符串?dāng)?shù)組的實例詳解的相關(guān)資料,主要是利用Collections.sort()方法對泛型為String的List 進(jìn)行排序,需要的朋友可以參考下
    2017-08-08

最新評論

遵义县| 海盐县| 德昌县| 教育| 永泰县| 育儿| 铁力市| 石泉县| 堆龙德庆县| 若羌县| 泗水县| 静海县| 邹平县| 安图县| 镇原县| 德钦县| 易门县| 孟州市| 鄂州市| 乌什县| 镇赉县| 永城市| 九江县| 右玉县| 湖口县| 华容县| 济源市| 泉州市| 西宁市| 桐城市| 旅游| 瑞安市| 保定市| 磴口县| 桂东县| 凭祥市| 拜泉县| 宜兰县| 桓仁| 福州市| 中卫市|