lock.unlock();
調用unlock方法,往下追
@Override
public void unlock() {try {// 1. 執行異步解鎖操作并同步等待結果// - 獲取當前線程ID作為鎖持有者標識// - unlockAsync()觸發Lua腳本執行實際解鎖// - get()方法阻塞直到異步操作完成get(unlockAsync(Thread.currentThread().getId()));} catch (RedisException e) {// 2. 異常處理:識別非法解鎖場景if (e.getCause() instanceof IllegalMonitorStateException) {// 2.1 特殊處理:當前線程非鎖持有者// - 通常在Lua腳本返回nil時觸發// - 表示嘗試釋放未被當前線程持有的鎖throw (IllegalMonitorStateException) e.getCause();} else {// 2.2 其他Redis異常(如連接問題)// - 網絡中斷、Redis宕機等場景throw e;}}// 3. 成功執行路徑:// - Lua腳本返回0(重入鎖部分釋放)或1(完全釋放)// - 后臺自動觸發看門狗任務取消(完全釋放時)
}
再往下追unlcokAsync這個異步方法
這里調用解鎖方法unlockInnerAsync同樣返回了RFutrue,當lua腳本執行完過后,RFutrue就會變成完成狀態,回調用回調函數onComplete,lua腳本就是再unlockInnerAsync里執行的,我們接著往下追
@Override
public RFuture<Void> unlockAsync(long threadId) {// 創建異步結果對象,用于返回解鎖操作最終狀態RPromise<Void> result = new RedissonPromise<Void>();// 執行核心解鎖操作(發送Lua腳本到Redis)RFuture<Boolean> future = unlockInnerAsync(threadId);// 注冊回調函數處理解鎖結果future.onComplete((opStatus, e) -> {// 關鍵步驟:無論解鎖成功與否,都取消看門狗續期任務// 防止鎖釋放后繼續續期(相當于"喂狗"操作停止)cancelExpirationRenewal(threadId);// 異常處理:Redis操作出錯if (e != null) {result.tryFailure(e); // 設置結果為失敗并傳遞異常return;}// 非法狀態檢查:opStatus為null表示解鎖失敗// 常見原因:嘗試釋放非當前線程持有的鎖if (opStatus == null) {// 構造詳細的非法狀態異常信息IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: "+ id + " thread-id: " + threadId);result.tryFailure(cause); // 設置結果為失敗return;}// 解鎖成功:設置結果為成功result.trySuccess(null);});// 返回異步結果對象return result;
}
protected RFuture<Boolean> unlockInnerAsync(long threadId) {return evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,-- 1. 驗證鎖持有者身份
if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then return nil; -- 非持有者嘗試解鎖
end; -- 2. 減少重入計數
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); -- 3. 判斷是否完全釋放
if (counter > 0) then -- 3.1 未完全釋放(重入場景)redis.call('pexpire', KEYS[1], ARGV[2]); -- 更新過期時間return 0; -- 返回未完全釋放標識
else -- 3.2 完全釋放redis.call('del', KEYS[1]); -- 刪除鎖redis.call('publish', KEYS[2], ARGV[1]); -- 發布解鎖消息return 1; -- 返回成功釋放標識
end; return nil; -- 默認返回(不會執行到這里)Arrays.asList(getName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId));}
?再執行完過后回取消定時任務,我們追進去,這里設計到全局靜態map EXPIRATION_RENEWAL_MAP 放一張流程圖方便回憶這個map
void cancelExpirationRenewal(Long threadId) {// 從全局MAP獲取EntryExpirationEntry entry = EXPIRATION_RENEWAL_MAP.get(getEntryName());if (entry != null) {// 關鍵操作:移除線程記錄entry.removeThreadId(threadId);// 檢查是否完全釋放if (entry.hasNoThreads()) {// 取消定時任務Timeout timeout = entry.getTimeout();if (timeout != null) {timeout.cancel();}// 從全局MAP移除EXPIRATION_RENEWAL_MAP.remove(getEntryName());}}
}
可以看到map存儲的是鎖的名稱和entry對象 entry對象里面放入了線程id,所以釋放的時候先從entry移除線程id,如果沒有了線程id再從map里移除entry對象