一、加鎖流程
1.?核心方法調用鏈
RLock lock = redisson.getLock("resource");
lock.lock(); // 阻塞式加鎖? lockInterruptibly()? tryAcquire(-1, leaseTime, unit) // leaseTime=-1表示啟用看門狗? tryAcquireAsync()? tryLockInnerAsync() // 執行Lua腳本
2.?Lua腳本實現(關鍵)
// RedissonLock.tryLockInnerAsync()
"if (redis.call('exists', KEYS[1]) == 0) then " + // 鎖不存在"redis.call('hset', KEYS[1], ARGV[2], 1); " + // 創建鎖(Hash結構)"redis.call('pexpire', KEYS[1], ARGV[1]); " + // 設置過期時間"return nil; " +
"end; " +
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " + // 鎖已存在,判斷是否重入"redis.call('hincrby', KEYS[1], ARGV[2], 1); " + // 重入次數+1"redis.call('pexpire', KEYS[1], ARGV[1]); " + // 重置過期時間"return nil; " +
"end; " +
"return redis.call('pttl', KEYS[1]);", // 返回剩余時間,加鎖失敗
Collections.singletonList(getName()), // KEYS[1]: 鎖名稱(如"resource")
internalLockLeaseTime, getLockName(threadId) // ARGV[1]: 過期時間;ARGV[2]: 線程標識(UUID:threadId)
3.?關鍵點
- 原子性:通過Lua腳本保證檢查鎖和創建鎖的原子性。
- 數據結構:使用Redis的
Hash
存儲鎖信息,field
為線程標識,value
為重入次數。 - 過期時間:默認30秒(看門狗機制自動續期),防止死鎖。
二、解鎖流程
1.?核心方法調用鏈
lock.unlock();? unlockAsync()? unlockInnerAsync() // 執行Lua腳本
2.?Lua腳本實現(關鍵)
// RedissonLock.unlockInnerAsync()
"if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " + // 鎖不存在或非當前線程持有"return nil; " +
"end; " +
"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " + // 重入次數-1
"if (counter > 0) then " + // 重入次數>0,繼續持有鎖"redis.call('pexpire', KEYS[1], ARGV[2]); " + // 重置過期時間"return 0; " +
"else " + // 重入次數=0,釋放鎖"redis.call('del', KEYS[1]); " + // 刪除鎖"redis.call('publish', KEYS[2], ARGV[1]); " + // 發布鎖釋放消息(通知等待線程)"return 1; " +
"end; " +
"return nil;",
Arrays.asList(getName(), getChannelName()), // KEYS[1]: 鎖名稱;KEYS[2]: 發布訂閱通道
LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(threadId) // ARGV[3]: 線程標識
3.?關鍵點
- 安全釋放:僅鎖持有者(
UUID:threadId
匹配)可釋放鎖。 - 發布訂閱:鎖釋放時通過Redis的
PUBLISH
通知等待線程。 - 重入處理:通過
hincrby -1
遞減重入次數,確保正確釋放。
三、鎖續時(看門狗機制)
1.?觸發條件
- 當使用無參
lock()
方法時(即未指定leaseTime
),默認啟用看門狗。 - 看門狗默認每10秒(
internalLockLeaseTime / 3
)續期一次,將鎖過期時間重置為30秒。
2.?核心源碼
// RedissonLock.scheduleExpirationRenewal()
private void scheduleExpirationRenewal(long threadId) {ExpirationEntry entry = new ExpirationEntry();ExpirationEntry oldEntry = EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry);entry.addThreadId(threadId);// 創建定時任務Timeout task = commandExecutor.getConnectionManager().newTimeout(timeout -> {RFuture<Boolean> future = renewExpirationAsync(threadId);future.whenComplete((res, e) -> {if (e != null) {log.error("Can't update lock " + getName() + " expiration", e);return;}if (res) {// 續期成功,遞歸調用scheduleExpirationRenewal(threadId);}});}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);entry.setTimeout(task);
}
3.?續期Lua腳本
// RedissonLock.renewExpirationAsync()
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " + // 鎖存在且為當前線程持有"redis.call('pexpire', KEYS[1], ARGV[1]); " + // 重置過期時間"return 1; " +
"end; " +
"return 0;",
Collections.singletonList(getName()),
internalLockLeaseTime, getLockName(threadId)
4.?關鍵點
- 自動續期:通過Netty的
Timeout
實現定時任務。 - 避免死鎖:若業務執行時間超長,看門狗會持續續期,直到業務完成或線程崩潰。
四、重入鎖實現
1.?數據結構
使用Redis的Hash
存儲鎖信息:
- Key:鎖名稱(如
"resource"
)。 - Field:線程標識(
UUID:threadId
)。 - Value:重入次數(初始為1,每次重入+1)。
2.?加鎖時的重入邏輯
// Lua腳本片段
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " + // 鎖已存在,判斷是否重入"redis.call('hincrby', KEYS[1], ARGV[2], 1); " + // 重入次數+1"redis.call('pexpire', KEYS[1], ARGV[1]); " + // 重置過期時間"return nil; " + // 返回nil表示加鎖成功(重入)
"end; "
3.?解鎖時的重入邏輯
// Lua腳本片段
"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " + // 重入次數-1
"if (counter > 0) then " + // 重入次數>0,繼續持有鎖"redis.call('pexpire', KEYS[1], ARGV[2]); " + // 重置過期時間"return 0; " + // 返回0表示鎖未釋放
"else " + // 重入次數=0,釋放鎖"redis.call('del', KEYS[1]); " + // 刪除鎖"return 1; " + // 返回1表示鎖已釋放
"end; "
4.?關鍵點
- 線程安全:通過
UUID:threadId
確保同一線程可重入。 - 原子計數:使用
hincrby
保證計數操作的原子性。
五、lock()與tryLock()的區別
1.?核心區別對比表
特性 |
|
|
阻塞行為 | 阻塞直到獲取鎖 | 立即返回或在指定時間內等待 |
超時機制 | 無超時,默認啟用看門狗自動續期 | 可自定義等待時間和鎖持有時間 |
異常處理 | 不響應中斷(拋出? | 可響應中斷(通過重載方法) |
返回值 |
|
|
看門狗默認啟用 | 是(無參時) | 否(需顯式設置超時參數) |
典型場景 | 必須獲取鎖才能執行的場景 | 可重試或放棄的場景 |
2.?源碼差異分析
// lock() 源碼片段
public void lock() {try {lock(-1, null, false); // leaseTime=-1表示啟用看門狗} catch (InterruptedException e) {Thread.currentThread().interrupt();}
}// tryLock() 源碼片段
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {// 1. 計算超時時間long time = unit.toMillis(waitTime);long current = System.currentTimeMillis();long threadId = Thread.currentThread().getId();// 2. 嘗試獲取鎖Long ttl = tryAcquire(waitTime, leaseTime, unit, threadId);if (ttl == null) {return true; // 獲取成功}// 3. 超時處理邏輯(循環嘗試或等待通知)// ...
}
3.?使用場景對比
// lock() 使用示例
RLock lock = redisson.getLock("order:123");
try {lock.lock(); // 阻塞直到獲取鎖// 執行關鍵業務邏輯
} finally {lock.unlock();
}// tryLock() 使用示例
RLock lock = redisson.getLock("inventory:apple");
try {// 嘗試在5秒內獲取鎖,持有30秒if (lock.tryLock(5, 30, TimeUnit.SECONDS)) {// 獲取鎖成功,執行操作} else {// 獲取鎖失敗,執行降級邏輯}
} catch (InterruptedException e) {Thread.currentThread().interrupt();
} finally {if (lock.isHeldByCurrentThread()) {lock.unlock();}
}
六、總結
Redisson分布式鎖的核心優勢:
- 原子性:通過Lua腳本確保操作的原子性。
- 可重入:基于
Hash
結構實現線程級別的重入計數。 - 高可用:通過看門狗機制避免鎖過期導致的數據不一致。
- 高性能:基于Netty的異步通信模型。
- 安全釋放:通過
UUID:threadId
確保鎖只能被持有者釋放。
最佳實踐建議:
- 優先使用?
tryLock()
:避免長時間阻塞,提高系統吞吐量。 - 明確鎖持有時間:根據業務場景合理設置
leaseTime
,避免過度依賴看門狗。 - 異常處理:使用帶超時參數的
tryLock()
,并處理中斷異常。