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

前言

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

本文源代碼: 源代碼下載

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

看到很多人都是這樣寫

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

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

圖片.png

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

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

例子1:驗證上面的寫法

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

      <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充分運行
            thread_2.start();
            TimeUnit.SECONDS.sleep(10); // 讓thread_2 等待鎖
            thread_2.interrupt(); // 中斷正在等待鎖的thread_2 觀察thread_2是否會不會拿到鎖
            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.");
        }
    }
}

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

接下來就需要觀察結(jié)果, 在線程thread-2中斷的時候會不會獲得鎖, 如何觀察呢? 因為我們知道如果一個線程嘗試去釋放一個屬于別的線程的鎖的時候, 會拋出一個運行時異常叫做異常, 另外我們也可以通過觀察redis里面數(shù)據(jù)的變化情況來判斷thread-2到底有沒有獲得鎖.

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

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

圖片.png

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

例子2: 嚴謹?shù)膶懛?/h1>

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

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充分運行
            thread_2.start();
            TimeUnit.SECONDS.sleep(10); // 讓thread_2 等待鎖
            thread_2.interrupt(); // 中斷正在等待鎖的thread_2 觀察thread_2是否會不會拿到鎖
            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ù)期, 沒有報異常, 線程都是正常退出.

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 摘要: 我們已經(jīng)知道,synchronized 是Java的關(guān)鍵字,是Java的內(nèi)置特性,在JVM層面實現(xiàn)了對臨界...
    kingZXY2009閱讀 1,881評論 0 20
  • Lock和synchronized synchronized都知道是用于同步代碼塊和方法的,線程一旦獲得對象鎖,其...
    耳_總閱讀 475評論 0 1
  • 從Java 5之后,在java.util.concurrent.locks包下提供了另外一種方式來實現(xiàn)同步訪問,那...
    薛晨閱讀 720評論 1 5
  • 方助生閱讀 153評論 0 0
  • 會有那樣的歲月嗎,天空的風(fēng)干凈的吹來,草地一望無垠,溫暖的陽光一點點鋪滿世界,我們都不再困惑。
    想念地瓜閱讀 257評論 0 0

友情鏈接更多精彩內(nèi)容