Redisson(2-2)分布式锁实现对比 VS Java的ReentrantLock之带超时时间的tryLock
Redisson實現(xiàn)了一整套JDK中ReentrantLock的功能,這里對比一下實現(xiàn)的差異和核心的思想。
?
unfair模式的帶超時時間的tryLock(超時時間)
ReentrantLock
這里上來會直接先試下能不能try成功,如果不成功,進(jìn)入等待并開始競爭等邏輯。
整個鎖的核心是通過LockSupport的park方法來實現(xiàn)的,這是調(diào)用底層UNSAFE的park方法來實現(xiàn)的。如果在被等待獲取的鎖釋放的時候,該線程會重新被喚醒,然后和其它線程一起競爭,如果沒有競爭成功,那么繼續(xù)park,循環(huán)往復(fù),直到獲取到鎖或者等待超時(這里park的時間是當(dāng)前要獲取鎖的線程等待獲取鎖的剩余時間,當(dāng)前線程要么因為鎖釋放被喚醒,要么等到超時)。
這里有個小細(xì)節(jié)是,如果剩余的等待時間小于spinForTimeoutThreshold這個值,那么就不會再park然后等待喚醒了。這是為了防止park然后喚醒這種線程調(diào)度操作,因為剩下的等待時間已經(jīng)不多,即使一直自旋也不會消耗太多的CPU性能,反而比頻繁掛起釋放要節(jié)省性能,這是一個折衷處理。
RedissonLock
RedissonLock沒有LockSupport這種底層級別的工具來幫忙,因為它是分布式的,所以它也需要借助這樣一個東西,來實現(xiàn)類似的功能。
我們一般在自己用Redis實現(xiàn)分布式鎖的時候,經(jīng)常設(shè)計的操作是輪詢Redis去查詢該鎖的狀態(tài),輪詢之間會設(shè)置一個休眠時間,休眠時間越短,當(dāng)鎖釋放的時候響應(yīng)的就越及時,但是對Redis的壓力就越大,因為你單位時間內(nèi)輪詢Redis的次數(shù)會增加,所以這是一個痛點。那么如果我們用通知來代替輪詢,是不是就可以仿照ReentrantLock那樣,通過喚醒操作(借助通知)來喚醒本地的sleep操作,這樣就不必定時輪詢檢查狀態(tài)了。
而這個功能就要利用Redis的訂閱功能,下面看代碼:
這里和ReentrantLock一樣,先try一下,如果獲得了鎖就返回,如果沒有獲得,看下是否已經(jīng)超過等待獲取鎖設(shè)置的超時時間,如果已經(jīng)超時,獲取鎖失敗,否則,就要進(jìn)入等待競爭的過程。
競爭的過程中,第一個操作是subcribe,即上面我們提到的訂閱功能。訂閱的超時時間即當(dāng)前要獲取鎖的線程的剩余等待時間,如果在這個時間范圍內(nèi)沒有響應(yīng),訂閱失敗。
訂閱的相關(guān)操作可以參考:https://my.oschina.net/u/2313177/blog/1925237。
“訂閱監(jiān)聽Redis消息,并且創(chuàng)建RedissonLockEntry,其中RedissonLockEntry中比較關(guān)鍵的是一個Semaphore屬性對象,用來控制本地的鎖請求的信號量同步,返回的是netty框架的Future實現(xiàn)”。
如果這個await成功了,代表在等待鎖的超時時間之內(nèi)鎖就被釋放了,接下來要進(jìn)入競態(tài)部分了。
這里我理解有兩層維度,一層是應(yīng)用維度,不同的虛擬機(jī)之間通過訂閱方式競爭鎖,某一臺業(yè)務(wù)服務(wù)器的java虛擬機(jī)會最終成功訂閱到這個鎖。二層是虛擬機(jī)內(nèi)的線程維度,這里機(jī)器內(nèi)的競爭通過一個共享鎖來減少對Redis的壓力,因為當(dāng)前虛擬機(jī)的訂閱下面掛著多個競爭該鎖的不同線程,這些線程中也只有一個會成功獲得共享鎖,繼而再去競爭真正的鎖。沒有獲得共享鎖的線程睡眠一段時間,或者被喚醒繼續(xù)獲取鎖,或者超時,獲取鎖失敗,而只有獲得共享鎖成功的線程才有機(jī)會和其它虛擬機(jī)中同樣獲取共享鎖的線程共同競爭真正的鎖。
所以同虛擬機(jī)內(nèi)要競爭真正鎖的所有線程先競爭一個共享鎖(Semaphore),成功的線程才獲取機(jī)會和其它虛擬機(jī)內(nèi)同樣獲取共享鎖(Semaphore)的線程競爭真正的鎖——跨虛擬機(jī)的鎖競爭,即分布式鎖競爭,這里的關(guān)鍵是共享鎖降低了靜態(tài),同時又利用訂閱機(jī)制(通知機(jī)制)解決了睡眠時間無法合理設(shè)置的問題。
其實redisson實現(xiàn)分布式鎖原理 - min.jiang - 博客園文章也說到了這個問題:
所以,如果訂閱成功,說明當(dāng)前機(jī)器在虛擬機(jī)層面首先搶占到了資源,然后再在當(dāng)前虛擬機(jī)內(nèi)進(jìn)行不同線程間的競爭。
像上面說的那樣,這里借助信號量來替代了ReentrantLock中的LockSupport的park功能,“通過信號量(共享鎖)阻塞,等待解鎖消息(這一點設(shè)計的非常精妙:減少了其他分布式節(jié)點的等待或者空轉(zhuǎn)等無效鎖申請的操作,整體提高了性能)。如果剩余時間(ttl)小于wait time ,就在 ttl 時間內(nèi),從Entry的信號量獲取一個許可(除非被中斷或者一直沒有可用的許可)。否則就在wait time時間范圍內(nèi)等待”。
這里的共享鎖和上面subscribe的是同一把鎖。如果本虛擬機(jī)的某個線程獲取鎖之后,當(dāng)它釋放鎖的時候,會發(fā)布一條取消訂閱的消息(這個后面具體會說),這樣其它虛擬機(jī)才能有平等的機(jī)會再次和本虛擬機(jī)的其他線程競爭鎖,而不是一直在本虛擬機(jī)的范圍內(nèi)進(jìn)行競爭,那樣其他虛擬機(jī)就會一直處于饑餓狀態(tài)。
那么什么時候這個訂閱的消息會解除當(dāng)前線程的休眠操作呢?和subscribe對應(yīng)的當(dāng)然就是publish了,當(dāng)執(zhí)獲得鎖的線程進(jìn)行解鎖操作的時候(解鎖后面會單獨說),在lua中會執(zhí)行‘publish’操作,而publish的消息類型是LockPubSub.unlockMessage,這個消息會觸發(fā)訂閱消息的共享鎖(Semaphore)的喚醒操作,然后發(fā)起對該鎖的新一輪競爭。
而最后無論獲取鎖成功與否,都要解除當(dāng)前線程對該鎖的訂閱消息。
關(guān)于等待時間(Semaphore的休眠時間),上面ReentrantLock的park的時間是當(dāng)前線程獲取鎖的等待剩余時間。這里本地等待鎖的睡眠時間略有不同,使用的是Redis中鎖的生命周期剩余時間,當(dāng)然在這個睡眠過程中,可能會因為鎖釋放而喚醒,因為有對當(dāng)前鎖的訂閱操作。
總結(jié)
以上是生活随笔為你收集整理的Redisson(2-2)分布式锁实现对比 VS Java的ReentrantLock之带超时时间的tryLock的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 最值得收藏的电脑使用习惯, 让你使用电脑
- 下一篇: android 图片存取方法,6种备份A