Redisson(2-1)分布式锁实现对比 VS Java的ReentrantLock之tryLock
?Redisson實現了一整套JDK中ReentrantLock的功能,這里對比一下實現的差異和核心的思想。
unfair模式的tryLock
ReentrantLock
①判斷當前的state是否是0(初始狀態),并用原子操作設置state,成功說明獲取鎖,并把當前線程設置為獲取鎖的線程。
②如果state不是0,檢查當前線程是否是持有鎖的線程,如果是就按照重入語義,增加計數,當然前提是不能超過最多重入的次數(不能溢出),然后將最終計數設置到state中。
③如果既不是①也不是②,那么tryLock失敗。
Redisson
因為這里的鎖是用Redis實現的,不在Java虛擬機內,所以只能用線程id來辨識鎖,無法通過調用Thread.currentThread()方法來做。無參的加鎖方法,leaseTime是-1,所以走的是if下面的邏輯,這個加鎖的邏輯是通過lua腳本來完成的。這個和ReentrantLock的加鎖方式不同的是,為了防止死鎖產生(參考上一篇博客),這里設置了一個看門狗,默認時間是30秒鐘,如果業務沒有處理完,會每隔10秒鐘延長這個鎖的時間。
具體加鎖的邏輯是通過lua腳本來完成,因為lua腳本會保證原子性。具體的lua腳本內容可以參見博客:https://my.oschina.net/u/2313177/blog/1919810。
Lua腳本中的執行分為以下三步:
①'exists', KEYS[1]) == 0 //exists檢查redis中是否存在當前要獲取的鎖
('hset', KEYS[1], ARGV[2], 1) //如果不存在,則獲取成功;同時設置鎖名稱KEYS[1],線程id[ARGV[2],鎖重入次數1
('hexpire', KEYS[1], ARGV[1]) 設置鎖的過期時間為ARGV[1],返回;
②('hexists', KEYS[1], ARGV[2])?==1? //如果檢查到存在KEYS[1],[ARGV[2],則說明獲取成功
('hincrby', KEYS[1], ARGV[2], 1) ?//此時會自增對應的value值,記錄重入次數
('hexpire', KEYS[1], ARGV[1]) //并更新鎖的過期時間
③key不存在
('pttl', KEYS[1] ) //直接返回key的剩余過期時間
上述一系列操作都是原子性的,所以沒有線程并發問題。
后面還有一段添加listener的操作,因為上面的lua腳本執行的redis指令是異步的調用,會返回一個future,這個future如果成功放回,同時返回的結果的Boolean值是true,說明獲取鎖成功了,這個時候會調用一個叫做scheduleExpirationRenewal的方法:
如果該鎖已經添加過了,那么返回,否則創建一個延時任務,該任務遞歸調用該任務本身。最后為了保證競態,防止多個線程同時寫,會將寫失敗的線程對應的任務cancel掉。這個方法的內容在上一篇博客中已經解釋了,就是redisson看門狗的作用。
這里有幾個問題需要注意一下:
①為什么說這里的返回值Boolean是true,就說明獲取鎖成功呢?
這里看下redis指令最后的一個commands參數:
結果的Boolean表示的是,返回值不等于null這個條件是否為真。這里如果Boolean是true,說明返回值是null。那么什么情況返回值是null呢?看上面分析的lua腳本,只有當當前線程id獲取鎖成功的時候,才會返回null,否則會返回ttl。
②tryAcquireAsync和tryAcquireOnceAsync
仔細看的話,會發現tryAcquireAsync中的判斷是否加鎖成功的邏輯和上述邏輯正好相反,
原因是因為這里的commands是
所以返回的就是指令本身,那么根據上述lua腳本,只有當獲取鎖的時候,返回值才是null,所以這里的判斷雖然和上面相反,但是邏輯本身是一模一樣的。
設計成這樣的原因是因為,帶once的方法需要返回Boolean,一錘子買賣,如果try失敗了再進行其他邏輯。而后面的方法,失敗后會進行復雜的鎖競爭處理(這個后面會繼續說)。
兩者實現的功能從接口上也能看出不同,前者是:
失敗就失敗了,繼續按照失敗或成功來進行業務邏輯即可。
后者是:
如果沒有立刻獲取到鎖,會在超時時間之內進行重試等復雜操作,直到獲取鎖或者超時為止。
總結
以上是生活随笔為你收集整理的Redisson(2-1)分布式锁实现对比 VS Java的ReentrantLock之tryLock的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 什么是高斯模糊算法?
- 下一篇: 1_11_4 23 python基础学习