1. 程式人生 > 其它 >[分散式鎖] [Redisson實現] -誤解-- 對lock方法的使用

[分散式鎖] [Redisson實現] -誤解-- 對lock方法的使用

前言

看了很多用redisson實現分散式鎖的部落格, 對他們使用的方式我個人認為有一點點自己的看法, 接下來本文將以例子來驗證為什麼會有誤解, 和看看正確的方式應該怎麼寫?

本文原始碼: 原始碼下載

大多數認為的寫法

看到很多人都是這樣寫

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

簡單看完原始碼後, 我看到該方法會去呼叫一個響應一箇中斷的lockInterruptibly,此時我就有點疑惑了, 響應中斷就是表示執行緒如果發生中斷就不會在等待佇列中等待(當然redisson

是採用SUB/PUB的方式),(本文不分析原始碼哈,對該鎖的原始碼分析會放到專門部落格裡面分析, 主要是驗證該如何使用)可以看下圖:

圖片.png

上圖中lock等方法會最終呼叫public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException 該方法會丟擲異常, 然而lock方法並沒有把這個異常丟擲給使用者, 而是採用捕獲異常,並且重新設定中斷狀態.

這下就有點明白了, 是不是需要使用者自己來判斷當前執行緒的狀態來判斷當前執行緒是否獲得鎖了呢?已經猜到這一步了, 接下來就需要驗證一下自己的猜想

例子1:驗證上面的寫法

我是用maven專案構建的一個小專案,因此加入如下依賴

      <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以此達到測試的目的.

接下來就需要觀察結果, 線上程thread-2中斷的時候會不會獲得鎖, 如何觀察呢? 因為我們知道如果一個執行緒嘗試去釋放一個屬於別的執行緒的鎖的時候, 會丟擲一個執行時異常叫做異常, 另外我們也可以通過觀察redis裡面資料的變化情況來判斷thread-2到底有沒有獲得鎖.

執行結果:

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還沒有結束的時候,也就是在thread-1在獲得鎖但是還沒有釋放鎖的時候, thread-2由於被別的執行緒中斷停止了等待從lock.lock(10, TimeUnit.MINUTES)的阻塞狀態中返回繼續執行接下來的邏輯,並且由於嘗試去釋放一個屬於執行緒thread-1的鎖而丟擲了一個執行時異常導致該執行緒thread-2結束了, 然而thread-2完成了一系列操作後,執行緒thread-1才釋放了自己的鎖. 所以thread-2並沒有獲得鎖,卻執行了需要同步的內容,還嘗試去釋放鎖.

從redis的角度看執行緒thread-2有沒有獲得鎖: 下圖便是整個執行期間KEY中內容的變化,從始至終redis中的testlockkey只產生了9f178836-f7e1-44fe-a89d-2db52f399c0d:20這一個key,很明顯這個key是屬於執行緒thread-1的,因為thread-1先獲得了鎖.如果thread-2獲得了執行緒, 在key9f178836-f7e1-44fe-a89d-2db52f399c0d:20消失後應該產生一個屬於執行緒thread-2的key.

圖片.png

總結: 總上面兩種角度的分析來看, thread-2在被別的執行緒中斷後並沒有獲得鎖, 所以這種寫法不嚴謹!

例子2: 嚴謹的寫法

看了例子1, 現在已經驗證了我們的想法是對的, 線上程發生中斷的時候該執行緒會立馬從阻塞狀態中返回, 並且沒有獲得鎖. 因此我們看看第二個例子看看如何寫會比較嚴謹.

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.");
        }
    }
}

結果如下: 符合預期, 沒有報異常, 執行緒都是正常退出.

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


https://www.jianshu.com/p/b12e1c0b3917