RedissonLock加鎖邏輯

加鎖邏輯.png

RedissonLock不同的加鎖方法浓恳,流程會有所差別:
tryLock()不帶參數(shù)最終調(diào)用的是

    private RFuture<Boolean> tryAcquireOnceAsync(long leaseTime, TimeUnit unit, long threadId) {
        if (leaseTime != -1) {
            return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
        }
        "只有l(wèi)easeTime為-1時才會,有延期鎖的作用(也就是看門狗),鎖的過期時間需要看配置,默認(rèn)是30S"
        RFuture<Boolean> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
        ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
            if (e != null) {
                return;
            }

            // lock acquired
           "獲取鎖成功后,調(diào)用定時任務(wù)延遲鎖時間"
            if (ttlRemaining) {
                scheduleExpirationRenewal(threadId);
            }
        });
        return ttlRemainingFuture;
    }

傳過來的參數(shù)leaseTime為-1皮获,unint是null,這個方法獲取不到直接就結(jié)束了纹冤,有點(diǎn)下failfast的模式洒宝,線程不會做自旋重試。
具體加鎖實(shí)現(xiàn):
tryLockInnerAsync:

    <T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
        internalLockLeaseTime = unit.toMillis(leaseTime);

        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
                  "if (redis.call('exists', KEYS[1]) == 0) then " +
                      "redis.call('hset', KEYS[1], ARGV[2], 1); " +
                      "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                      "return nil; " +
                  "end; " +
                  "if (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.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
    }

通過這種eval表達(dá)式萌京,lua腳本保證原子性
如果不存在鎖:
等價于:

命令 備注
EXISTS lockkey 判斷鎖是否存在
HSET lockkey uuid:threadId 1 設(shè)置hash field和value值
PEXPIRE lockkey internalLockLeaseTime 設(shè)置lockkey的過期時間

如果存在鎖:
等價于:

命令 備注
HEXISTS lockkey uuid:threadId 判斷當(dāng)前線程是否已經(jīng)獲取到鎖
HSET lockkey uuid:threadId 1 設(shè)置hash field和value值
HINCRBY lockkey uuid:threadId 1 給對應(yīng)的field的值加1(相當(dāng)于可重入)
PEXPIRE lockkey internalLockLeaseTime 重置過期時間

普通的加鎖邏輯就結(jié)束了雁歌。
tryLock帶參數(shù)就相對復(fù)雜一些,加入了線程自旋相關(guān)的邏輯處理:

public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
        long time = unit.toMillis(waitTime);
        long current = System.currentTimeMillis();
        long threadId = Thread.currentThread().getId();
       "走tryAcquireAsync的邏輯"
        Long ttl = tryAcquire(leaseTime, unit, threadId);
        // lock acquired
        if (ttl == null) {
            return true;
        }
        
        time -= System.currentTimeMillis() - current;
        if (time <= 0) {
            "如果已超時知残,則直接失敗"
            acquireFailed(threadId);
            return false;
        }
        
        current = System.currentTimeMillis();
        RFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);
        if (!await(subscribeFuture, time, TimeUnit.MILLISECONDS)) {
            if (!subscribeFuture.cancel(false)) {
                subscribeFuture.onComplete((res, e) -> {
                    if (e == null) {
                        unsubscribe(subscribeFuture, threadId);
                    }
                });
            }
            acquireFailed(threadId);
            return false;
        }

        try {
            time -= System.currentTimeMillis() - current;
            if (time <= 0) {
                acquireFailed(threadId);
                return false;
            }
        
            while (true) {
                long currentTime = System.currentTimeMillis();
                ttl = tryAcquire(leaseTime, unit, threadId);
                // lock acquired
                if (ttl == null) {
                    return true;
                }

                time -= System.currentTimeMillis() - currentTime;
                if (time <= 0) {
                    acquireFailed(threadId);
                    return false;
                }

                // waiting for message
                currentTime = System.currentTimeMillis();
                if (ttl >= 0 && ttl < time) {
                    getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                } else {
                    getEntry(threadId).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
                }

                time -= System.currentTimeMillis() - currentTime;
                if (time <= 0) {
                    acquireFailed(threadId);
                    return false;
                }
            }
        } finally {
            unsubscribe(subscribeFuture, threadId);
        }
//        return get(tryLockAsync(waitTime, leaseTime, unit));
    }

如果客戶端宕機(jī)靠瞎,就不會在續(xù)期,那鎖到了30s之后自然就失效了

鎖續(xù)期邏輯:

    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;
                }
                
                RFuture<Boolean> future = renewExpirationAsync(threadId);
                future.onComplete((res, e) -> {
                    if (e != null) {
                        log.error("Can't update lock " + getName() + " expiration", e);
                        return;
                    }
                    
                    // reschedule itself
                    renewExpiration();
                });
            }
        }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
        
        ee.setTimeout(task);
    }

特別注意:以上過程存在一個細(xì)節(jié)求妹,這里有必要說明一下乏盐,也是分布式鎖的一個關(guān)鍵點(diǎn):當(dāng)鎖正在被占用時,\color{red}{等待獲取鎖的進(jìn)程并不是通過一個 while(true) 死循環(huán)去獲取鎖制恍,而是利用了 Redis 的發(fā)布訂閱機(jī)制,通過 await 方法阻塞等待鎖的進(jìn)程}丑勤,有效的解決了無效的鎖申請浪費(fèi)資源的問題。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末吧趣,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌强挫,老刑警劉巖岔霸,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異俯渤,居然都是意外死亡呆细,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門八匠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來絮爷,“玉大人,你說我怎么就攤上這事梨树】雍唬” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵抡四,是天一觀的道長柜蜈。 經(jīng)常有香客問我,道長指巡,這世上最難降的妖魔是什么淑履? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮藻雪,結(jié)果婚禮上秘噪,老公的妹妹穿的比我還像新娘。我一直安慰自己勉耀,他們只是感情好指煎,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著瑰排,像睡著了一般贯要。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上椭住,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天崇渗,我揣著相機(jī)與錄音,去河邊找鬼京郑。 笑死宅广,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的些举。 我是一名探鬼主播跟狱,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼户魏!你這毒婦竟也來了驶臊?” 一聲冷哼從身側(cè)響起挪挤,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎关翎,沒想到半個月后扛门,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡纵寝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年论寨,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片爽茴。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡葬凳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出室奏,到底是詐尸還是另有隱情火焰,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布窍奋,位于F島的核電站荐健,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏琳袄。R本人自食惡果不足惜江场,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望窖逗。 院中可真熱鬧址否,春花似錦、人聲如沸碎紊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽仗考。三九已至音同,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間秃嗜,已是汗流浹背权均。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留锅锨,地道東北人叽赊。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像必搞,于是被迫代替她去往敵國和親必指。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容

  • 我們不生產(chǎn)代碼,我們是代碼的搬運(yùn)工 前不久恕洲,阿里大牛蝦總再次拋出了分布式鎖的討論塔橡,對照之前項(xiàng)目中實(shí)現(xiàn)的redis分...
    碼農(nóng)戲碼閱讀 597評論 0 4
  • 前言 單機(jī)環(huán)境下我們可以通過JAVA的Synchronized和Lock來實(shí)現(xiàn)進(jìn)程內(nèi)部的鎖梅割,但是隨著分布式應(yīng)用和集...
    Java黎先生閱讀 731評論 0 1
  • 轉(zhuǎn)自:http://tech.lede.com/2017/03/08/rd/server/Redisson/ 針對...
    王帥199207閱讀 1,542評論 0 4
  • 分布式鎖 什么是鎖?使用鎖的目的是為了控制程序的執(zhí)行順序谱邪,防止共享資源被多個線程同時訪問炮捧。為了實(shí)現(xiàn)多個線程在一個時...
    緣起緣散_f1a7閱讀 219評論 0 0
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月,有人笑有人哭惦银,有人歡樂有人憂愁,有人驚喜有人失落末誓,有的覺得收獲滿滿有...
    陌忘宇閱讀 8,523評論 28 53