前言
在上一篇文章中已經分析過公平鎖的加鎖源碼,並得出結論:
- Redis Hash 數據結構:存放當前鎖,Redis Key 就是鎖,Hash 的 field 是加鎖線程,Hash 的 value 是 重入次數;
- Redis List 數據結構:充當線程等待隊列,新的等待線程會使用 rpush 命令放在隊列右邊;
- Redis sorted set 有序集合數據結構:存放等待線程的順序,分數 score 用來是等待線程的超時時間戳。
現在看一下加鎖失敗被放到等待隊列之后,線程是如何處理的?
排隊等鎖
源碼入口:org.redisson.RedissonLock#lock(long, java.util.concurrent.TimeUnit, boolean)
。
線程進入排隊之后,在 Java 代碼中會 while (true)
一直循環調用 tryAcquire
,嘗試獲取鎖。
最終還是來到 RedissonFairLock#tryLockInnerAsync
方法中。
方便起見,重新貼一下 Lua 腳本,以及腳本的參數含義。
- KEYS[1]:加鎖的名字,
anyLock
; - KEYS[2]:加鎖等待隊列,
redisson_lock_queue:{anyLock}
; - KEYS[3]:等待隊列中線程鎖時間的 set 集合,
redisson_lock_timeout:{anyLock}
,是按照鎖的時間戳存放到集合中的; - ARGV[1]:鎖超時時間 30000
- ARGV[2]:UUID:ThreadId 組合
a3da2c83-b084-425c-a70f-5d9a08b37f31:1
- ARGV[3]:threadWaitTime 默認 300000
- ARGV[4]:currentTime 當前時間戳
源碼分析
第一部分,while 循環:
- 從等待隊列
redisson_lock_queue:{anyLock}
中獲取第一個等待線程; - 從等待線程超時集合
redisson_lock_timeout:{anyLock}
中獲取第一個等待線程的分數; - 沒有超時,直接結束,超時了,則直接移除。
第二部分,當前鎖存在,直接跳過。
第三部分,當前鎖不是持鎖線程,直接跳過。
第四部分,
直接返回當前鎖還有多久到期。
當前 Redisson 版本為 3.15.6,不同版本的略有不同。
隊列重排
這里不存在重新排序,因為官方認為這是一個 bug,重新進行了修復。
具體可以閱讀:Justin Corpron 2019/5/10, 04:13 Fix timeout drift in RedissonFairLock
最大的變化就是增加了第四部分。
圖僅僅代表兩個版本的差別,並不是代表這個版本才修改。
總結
當線程獲取鎖失敗,進入到等待隊列時,ttl != null
,在 Java 代碼中會不斷嘗試獲取鎖。
當鎖不存在且當前線程是在等待隊列頭時,直接獲得鎖。這個排隊的過程就是公平鎖的提現。