[分布式鎖] [Redisson實(shí)現(xiàn)] --- 對(duì)lock方法的使用誤解

前言

看了很多用redisson實(shí)現(xiàn)分布式鎖的博客, 對(duì)他們使用的方式我個(gè)人認(rèn)為有一點(diǎn)點(diǎn)自己的看法, 接下來(lái)本文將以例子來(lái)驗(yàn)證為什么會(huì)有誤解, 和看看正確的方式應(yīng)該怎么寫?

本文源代碼: 源代碼下載

大多數(shù)認(rèn)為的寫法

看到很多人都是這樣寫

RLock lock = redisson.getLock(KEY);
lock.lock()
// do your own work
lock.unlock()

簡(jiǎn)單看完源代碼后, 我看到該方法會(huì)去調(diào)用一個(gè)響應(yīng)一個(gè)中斷的lockInterruptibly,此時(shí)我就有點(diǎn)疑惑了, 響應(yīng)中斷就是表示線程如果發(fā)生中斷就不會(huì)在等待隊(duì)列中等待(當(dāng)然redisson是采用SUB/PUB的方式),(本文不分析源碼哈,對(duì)該鎖的源碼分析會(huì)放到專門博客里面分析, 主要是驗(yàn)證該如何使用)可以看下圖:

圖片.png

上圖中lock等方法會(huì)最終調(diào)用public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException 該方法會(huì)拋出異常, 然而lock方法并沒有把這個(gè)異常拋出給使用者, 而是采用捕獲異常,并且重新設(shè)置中斷狀態(tài).

這下就有點(diǎn)明白了, 是不是需要用戶自己來(lái)判斷當(dāng)前線程的狀態(tài)來(lái)判斷當(dāng)前線程是否獲得鎖了呢?已經(jīng)猜到這一步了, 接下來(lái)就需要驗(yàn)證一下自己的猜想

例子1:驗(yàn)證上面的寫法

我是用maven項(xiàng)目構(gòu)建的一個(gè)小項(xiàng)目,因此加入如下依賴

      <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>2.7.0</version>
      </dependency>

加入以下例子.

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.config.Config;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class TestDistributedRedisLock {

    private static CountDownLatch finish = new CountDownLatch(2);
    private static final String KEY = "testlock";
    private static Config config;
    private static Redisson redisson;
    static {
        config = new Config();
        config.useSingleServer().setAddress("127.0.0.1:6379");
        redisson = (Redisson)Redisson.create(config);
    }

    public static void main(String[] args) {
        Thread thread_1 = new LockWithoutBoolean("thread-1");
        Thread thread_2 = new LockWithoutBoolean("thread-2");
        thread_1.start();
        try {
            TimeUnit.SECONDS.sleep(10); // 睡10秒鐘 為了讓thread_1充分運(yùn)行
            thread_2.start();
            TimeUnit.SECONDS.sleep(10); // 讓thread_2 等待鎖
            thread_2.interrupt(); // 中斷正在等待鎖的thread_2 觀察thread_2是否會(huì)不會(huì)拿到鎖
            finish.await();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            redisson.shutdown();
        }
    }

    static class LockWithoutBoolean extends Thread {
        private String name;
        public LockWithoutBoolean(String name) {
            super(name);
        }
        public void run() {
            RLock lock = redisson.getLock(KEY);
            lock.lock(10, TimeUnit.MINUTES);
            System.out.println(Thread.currentThread().getName() + " gets lock. and interrupt: " + Thread.currentThread().isInterrupted());
            try {
                TimeUnit.MINUTES.sleep(1);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                try {
                    lock.unlock();
                } finally {
                    finish.countDown();
                }
            }
            System.out.println(Thread.currentThread().getName() + " ends.");
        }
    }
}

在該例子中我啟動(dòng)了兩個(gè)線程分別為thread-1thread-2, 并且讓線程thread-1獲得鎖后休息1分鐘, 線程thread-2在等待鎖的過程中用主線程中斷線程thread-2以此達(dá)到測(cè)試的目的.

接下來(lái)就需要觀察結(jié)果, 在線程thread-2中斷的時(shí)候會(huì)不會(huì)獲得鎖, 如何觀察呢? 因?yàn)槲覀冎廊绻粋€(gè)線程嘗試去釋放一個(gè)屬于別的線程的鎖的時(shí)候, 會(huì)拋出一個(gè)運(yùn)行時(shí)異常叫做異常, 另外我們也可以通過觀察redis里面數(shù)據(jù)的變化情況來(lái)判斷thread-2到底有沒有獲得鎖.

運(yùn)行結(jié)果:

thread-1 gets lock. and interrupt: false
thread-2 gets lock. and interrupt: true
Exception in thread "thread-2" java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: 9f178836-f7e1-44fe-a89d-2db52f399c0d thread-id: 21
    at org.redisson.RedissonLock.unlock(RedissonLock.java:353)
    at com.example.TestDistributedRedisLock$LockWithoutBoolean.run(TestDistributedRedisLock.java:53)
thread-1 ends.

從程序的角度看線程thread-2有沒有獲得鎖: 可以看到在thread-1還沒有結(jié)束的時(shí)候,也就是在thread-1在獲得鎖但是還沒有釋放鎖的時(shí)候, thread-2由于被別的線程中斷停止了等待從lock.lock(10, TimeUnit.MINUTES)的阻塞狀態(tài)中返回繼續(xù)執(zhí)行接下來(lái)的邏輯,并且由于嘗試去釋放一個(gè)屬于線程thread-1的鎖而拋出了一個(gè)運(yùn)行時(shí)異常導(dǎo)致該線程thread-2結(jié)束了, 然而thread-2完成了一系列操作后,線程thread-1才釋放了自己的鎖. 所以thread-2并沒有獲得鎖,卻執(zhí)行了需要同步的內(nèi)容,還嘗試去釋放鎖.

從redis的角度看線程thread-2有沒有獲得鎖: 下圖便是整個(gè)運(yùn)行期間KEY中內(nèi)容的變化,從始至終redis中的testlockkey只產(chǎn)生了9f178836-f7e1-44fe-a89d-2db52f399c0d:20這一個(gè)key,很明顯這個(gè)key是屬于線程thread-1的,因?yàn)?code>thread-1先獲得了鎖.如果thread-2獲得了線程, 在key9f178836-f7e1-44fe-a89d-2db52f399c0d:20消失后應(yīng)該產(chǎn)生一個(gè)屬于線程thread-2的key.

圖片.png

總結(jié): 總上面兩種角度的分析來(lái)看, thread-2在被別的線程中斷后并沒有獲得鎖, 所以這種寫法不嚴(yán)謹(jǐn)!

例子2: 嚴(yán)謹(jǐn)?shù)膶懛?/h1>

看了例子1, 現(xiàn)在已經(jīng)驗(yàn)證了我們的想法是對(duì)的, 在線程發(fā)生中斷的時(shí)候該線程會(huì)立馬從阻塞狀態(tài)中返回, 并且沒有獲得鎖. 因此我們看看第二個(gè)例子看看如何寫會(huì)比較嚴(yán)謹(jǐn).

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.config.Config;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class TestDistributedRedisLockWithBool {
    private static CountDownLatch finish = new CountDownLatch(2);
    private static final String KEY = "testlock";
    private static Config config;
    private static Redisson redisson;
    static {
        config = new Config();
        config.useSingleServer().setAddress("127.0.0.1:6379");
        redisson = (Redisson)Redisson.create(config);
    }

    public static void main(String[] args) {
        Thread thread_1 = new LockWithBoolean("thread-1");
        Thread thread_2 = new LockWithBoolean("thread-2");
        thread_1.start();
        try {
            TimeUnit.SECONDS.sleep(10); // 睡10秒鐘 為了讓thread_1充分運(yùn)行
            thread_2.start();
            TimeUnit.SECONDS.sleep(10); // 讓thread_2 等待鎖
            thread_2.interrupt(); // 中斷正在等待鎖的thread_2 觀察thread_2是否會(huì)不會(huì)拿到鎖
            finish.await();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            redisson.shutdown();
        }
    }

    static class LockWithBoolean extends Thread {
        private String name;

        public LockWithBoolean(String name) {
            super(name);
        }

        public void run() {
            RLock lock = redisson.getLock(KEY);
            lock.lock(10, TimeUnit.MINUTES);
            if (!Thread.currentThread().isInterrupted()) {
                System.out.println(Thread.currentThread().getName() + " gets lock.");
                try {
                    TimeUnit.MINUTES.sleep(1);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    lock.unlock();
                }
            } else {
                System.out.println(Thread.currentThread().getName() + " does not get lock.");
            }
            System.out.println(Thread.currentThread().getName() + " ends.");
        }
    }
}

結(jié)果如下: 符合預(yù)期, 沒有報(bào)異常, 線程都是正常退出.

thread-1 gets lock.
thread-2 does not get lock.
thread-2 ends.
thread-1 ends.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末黑竞,一起剝皮案震驚了整個(gè)濱河市懂酱,隨后出現(xiàn)的幾起案子穆刻,更是在濱河造成了極大的恐慌狰域,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件灸异,死亡現(xiàn)場(chǎng)離奇詭異胧卤,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)啦粹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門偿荷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人唠椭,你說(shuō)我怎么就攤上這事跳纳。” “怎么了贪嫂?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵寺庄,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我力崇,道長(zhǎng)斗塘,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任亮靴,我火速辦了婚禮馍盟,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘茧吊。我一直安慰自己贞岭,他們只是感情好八毯,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著曹步,像睡著了一般宪彩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上讲婚,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天尿孔,我揣著相機(jī)與錄音,去河邊找鬼筹麸。 笑死活合,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的物赶。 我是一名探鬼主播白指,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼酵紫!你這毒婦竟也來(lái)了告嘲?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤奖地,失蹤者是張志新(化名)和其女友劉穎橄唬,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體参歹,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡仰楚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了犬庇。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片僧界。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖臭挽,靈堂內(nèi)的尸體忽然破棺而出捂襟,到底是詐尸還是另有隱情,我是刑警寧澤欢峰,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布葬荷,位于F島的核電站,受9級(jí)特大地震影響赤赊,放射性物質(zhì)發(fā)生泄漏闯狱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一抛计、第九天 我趴在偏房一處隱蔽的房頂上張望哄孤。 院中可真熱鬧,春花似錦吹截、人聲如沸瘦陈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)晨逝。三九已至蛾默,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間捉貌,已是汗流浹背支鸡。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留趁窃,地道東北人牧挣。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像醒陆,于是被迫代替她去往敵國(guó)和親瀑构。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

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

  • 摘要: 我們已經(jīng)知道刨摩,synchronized 是Java的關(guān)鍵字寺晌,是Java的內(nèi)置特性,在JVM層面實(shí)現(xiàn)了對(duì)臨界...
    kingZXY2009閱讀 1,829評(píng)論 0 20
  • Lock和synchronized synchronized都知道是用于同步代碼塊和方法的澡刹,線程一旦獲得對(duì)象鎖呻征,其...
    耳_總閱讀 430評(píng)論 0 1
  • 從Java 5之后,在java.util.concurrent.locks包下提供了另外一種方式來(lái)實(shí)現(xiàn)同步訪問像屋,那...
    薛晨閱讀 702評(píng)論 1 5
  • 方助生閱讀 141評(píng)論 0 0
  • 會(huì)有那樣的歲月嗎怕犁,天空的風(fēng)干凈的吹來(lái)边篮,草地一望無(wú)垠己莺,溫暖的陽(yáng)光一點(diǎn)點(diǎn)鋪滿世界,我們都不再困惑戈轿。
    想念地瓜閱讀 159評(píng)論 0 0