[分布式鎖] [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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM