可重入
Redisson 的鎖支持 可重入性,這意味著同一個線程在獲取鎖后,如果再次嘗試獲取該鎖,它可以成功地獲得鎖,而不會被阻塞。
- 每次一個線程成功獲取鎖后,它的持有次數會增加。當線程再次獲取該鎖時,Redisson 會檢查該線程是否已經持有鎖。如果是,它會允許該線程再次獲取鎖,并將持有次數遞增。
- 每次釋放鎖時,持有次數會遞減,直到持有次數變為零,鎖才會被完全釋放。
public static void main(String[] args) {// 創建 Redisson 客戶端配置Config config = new Config();config.useSingleServer().setAddress("redis://localhost:6379"); // 連接到本地 Redis 服務器RedissonClient redisson = Redisson.create(config);// 獲取分布式鎖RLock lock = redisson.getLock("accountLock");try {// 模擬賬戶操作的過程:先獲取鎖,進行第一次操作lock.lock(); //+1System.out.println("Lock acquired for the first time!");// 業務邏輯:扣款deductBalance(lock);} finally {// 釋放鎖:必須要釋放與 lock.lock() 相同次數的 unlock() 才能完全釋放鎖lock.unlock(); //-1System.out.println("Lock released after first operation.");}}// 模擬業務邏輯:扣款操作private static void deductBalance(RLock lock) {// 業務邏輯需要在同一個線程中再次獲取鎖(模擬可重入性)lock.lock();//+1try {System.out.println("Lock acquired for the second time, performing deduct balance operation...");// 扣款邏輯,比如賬戶余額減少System.out.println("Balance deducted!");} finally {// 釋放鎖lock.unlock();//-1System.out.println("Lock released after deduct balance operation.");}}
當value值為0時就會釋放鎖。
這就是鎖的可重入性。
看門狗機制
看門狗機制用于確保分布式系統中,鎖或資源的持有者在預定時間內釋放鎖,否則看門狗會自動釋放該鎖,避免死鎖等問題。幫助避免因某些異常(如程序崩潰或線程掛起)而導致鎖被永久占用。
-
自動續期:定期地自動刷新鎖的過期時間。當鎖被一個線程或進程持有時,會定期更新鎖的 TTL(過期時間),以防止它被過期。即使持鎖者在持有鎖的過程中沒有顯式地釋放鎖,會確保鎖不會在持有者不主動釋放時過期,從而避免因過期造成的錯誤。
-
自動解鎖:如果持鎖者超時或出現故障,會在檢測到持鎖者沒有繼續更新鎖的超時時間后,自動釋放鎖,避免死鎖。
-
配置超時時間:可以與鎖的過期時間和續期機制結合使用,可以通過配置來指定超時時間和自動續期的時間間隔。
相關源代碼
嘗試獲取鎖,如果不能獲取鎖,一直等待直到獲取成功或者達到超時限制。中間部分涉及到鎖的租期和超時處理,保證鎖的可用性和避免死鎖。interruptibly 參數提供了是否支持中斷等待的選項。
private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {// 獲取當前線程的ID,用于標識是哪一個線程請求鎖long threadId = Thread.currentThread().getId();// 嘗試獲取鎖,并獲得鎖的剩余有效時間 ttlLong ttl = this.tryAcquire(-1L, leaseTime, unit, threadId);// 如果成功獲得鎖(ttl != null),則進入鎖處理邏輯if (ttl != null) {// 訂閱當前線程的鎖操作,返回一個 RFuture 對象,用于后續的異步操作RFuture<RedissonLockEntry> future = this.subscribe(threadId);// 根據 interruptibly 參數決定同步執行訂閱操作if (interruptibly) {// 如果需要響應中斷,使用帶中斷支持的同步方法this.commandExecutor.syncSubscriptionInterrupted(future);} else {// 如果不需要響應中斷,直接使用同步方法this.commandExecutor.syncSubscription(future);}try {// 啟動一個無限循環來持續嘗試獲取鎖while (true) {// 每次進入循環時,重新嘗試獲取鎖的剩余時間 ttlttl = this.tryAcquire(-1L, leaseTime, unit, threadId);// 如果 ttl 為 null,表示鎖已經過期或無法獲取,跳出循環if (ttl == null) {return;}// 如果 ttl >= 0L,表示可以繼續持有鎖if (ttl >= 0L) {try {// 嘗試在 ttl 指定的時間內獲取 latch((RedissonLockEntry) future.getNow()).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);} catch (InterruptedException var13) {// 如果在獲取 latch 時發生 InterruptedException,則根據 interruptibly 變量決定是否拋出異常if (interruptibly) {// 如果需要響應中斷,拋出 InterruptedExceptionthrow var13;}// 如果不需要響應中斷,嘗試繼續獲取 latch((RedissonLockEntry) future.getNow()).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);}} else if (interruptibly) {// 如果 ttl 為負值并且需要響應中斷,調用 acquire 方法阻塞直到獲取鎖((RedissonLockEntry) future.getNow()).getLatch().acquire();} else {// 如果 ttl 為負值并且不需要響應中斷,調用 acquireUninterruptibly 方法阻塞直到獲取鎖((RedissonLockEntry) future.getNow()).getLatch().acquireUninterruptibly();}}} finally {// 最終,取消訂閱,釋放資源this.unsubscribe(future, threadId);}}
}
- 自動續期:通過 ttl 值和 latch.tryAcquire 方法,鎖的超時時間會在持有鎖的線程未釋放鎖的情況下自動延長,避免鎖超時。
- 避免死鎖:如果線程因為異常等原因沒有釋放鎖,看門狗機制會確保鎖最終被釋放。