您好,登錄后才能下訂單哦!
本文小編為大家詳細介紹“基于Redis的SETNX操作怎么實現分布式鎖”,內容詳細,步驟清晰,細節處理妥當,希望這篇“基于Redis的SETNX操作怎么實現分布式鎖”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學習新知識吧。
import com.jd.jim.cli.Cluster;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
/**
* <pre>
* 基于Redis的SETNX操作實現的分布式鎖
* </pre>
* @author lzc.java@icloud.com
*
*/
public class RedisDistributedLock {
private Cluster redis;
// 鎖的名字
private String lockKey;
// 鎖的值
private String lockVal = "";
// 默認鎖的有效時長(毫秒)
private long lockExpires;
private boolean locked;
// 當前jvm內持有該鎖的線程(if have one)
private Thread exclusiveOwnerThread;
/**
*
* @param redis
* @param lockKey lockKey
* @param lockExpires lockKey過期時間,單位:毫秒
* @throws IOException
*/
public RedisDistributedLock(Cluster redis, String lockKey, long lockExpires){
this.redis = redis;
this.lockKey = lockKey;
this.lockExpires = lockExpires;
}
/**
* 阻塞式獲取鎖 ,不過有超時時間,超過了tryGetLockTime還未獲取到鎖將直接返回false
* @param tryGetLockTime
* @param tryGetLockUnit
* @return
* @throws InterruptedException
*/
protected boolean lock(long tryGetLockTime, TimeUnit tryGetLockUnit){
try {
// 超時控制 的時間可以從本地獲取, 因為這個和鎖超時沒有關系, 只是一段時間區間的控制
long start = System.currentTimeMillis();
long timeout = tryGetLockUnit.toMillis(tryGetLockTime);
//int tryTimes=0;
while (System.currentTimeMillis() - start < timeout) {
//tryTimes++;
//鎖超時時間
long lockExpireTime = System.currentTimeMillis() + lockExpires + 1;
String stringOfLockExpireTime = String.valueOf(lockExpireTime);
if (setnx(lockKey, stringOfLockExpireTime)) { // 獲取到鎖
// 成功獲取到鎖, 設置相關標識
locked = true;
exclusiveOwnerThread = Thread.currentThread();
//System.out.println("拿到鎖了,哈哈:"+tryTimes);
return true;
}
//說明未獲取到鎖,進一步檢查鎖是否已經超時
String lockVal=redis.get(lockKey);
//是存在lockVal=null的情況的,C1客戶端獲取鎖,并且處理完后,DEL掉鎖,在DEL鎖之前。
// C2通過SETNX向lockKey設置時間戳T0 發現有客戶端已經獲取鎖,進入GET操作。
// 這時候C1客戶端DEL掉鎖成功。
// C2向lockKey發送GET命令,獲取返回值T1(null)。
if(lockVal!=null&&Long.parseLong(lockVal)<System.currentTimeMillis()){
//表明已經超時了,原來的線程可能可能出現意外未能及時釋放鎖
String oldLockVal=redis.getSet(lockKey,stringOfLockExpireTime);
//為什么會有下面這個判斷呢?因為多線程情況下可能同時有多個線程在這一時刻發現鎖過期,那么就會同時執行getSet獲取鎖操作,
//通過下面的比較,可以找到第一個執行getSet操作的線程,讓其獲得鎖,其它的線程則重試
//oldLockVal也存在null的情況,大家可以想想為什么
if(lockVal.equals(oldLockVal)){
redis.expire(lockKey, lockExpires, TimeUnit.MILLISECONDS);
// 成功獲取到鎖, 設置相關標識
locked = true;
exclusiveOwnerThread = Thread.currentThread();
return true;
}
}
Thread.sleep(5L);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
/**
* 非阻塞,立即返回是否獲取到鎖
* @return
*/
public boolean tryLock() {
if (setnx(lockKey, lockVal)) { // 獲取到鎖
// 成功獲取到鎖, 設置相關標識
locked = true;
//setExclusiveOwnerThread(Thread.currentThread());
exclusiveOwnerThread = Thread.currentThread();
return true;
}
return false;
}
private boolean setnx(String lockKey, Object val) {
if (redis.setNX(lockKey, String.valueOf(val))) {
redis.expire(lockKey, lockExpires, TimeUnit.MILLISECONDS);
return true;
}
return false;
}
public boolean isLocked() {
return locked;
}
/**
* 釋放鎖
*/
public void unlock() {
// 檢查當前線程是否持有鎖
if (Thread.currentThread() != exclusiveOwnerThread) {
// 表明鎖并非當前線程所持有,不應該由當前線程來釋放鎖
System.out.println("表明鎖并非當前線程所持有,不應該由當前線程來釋放鎖exclusiveOwnerThread:" + exclusiveOwnerThread + ",Thread.currentThread():"+Thread.currentThread()+",lockKey" + lockKey);
return;
}
//gaohongtianluck 忽略了一個地方。用del命令釋放鎖,如果線程A獲得鎖之后運行太久,久到另已經獲得的鎖失效了。
// 這時線程B進來,取締了A上的鎖,線程B運行到一半的時候,這時線程A也運行完了,殺一個回馬槍把原本以為獲取到的鎖給del,
// 實際上是B獲得的鎖,那么就會導致其他線程進來競爭,而B還以為自己獨占鎖
//回復Ffadsfoadfjaodjfalkd:我也在思考這個問題,我覺得有一種寫法可以盡量避免。在鎖的時候,如果鎖住了,回傳超時時間,作為解鎖時候的憑證,解鎖時傳入鎖的鍵值和憑證。我思考的解鎖時候有兩種寫法:
//1、解鎖前get一下鍵值的value,判斷是不是和自己的憑證一樣。但這樣存在一些問題:
//1)get時返回null的可能,此時表示有別的線程拿到鎖并用完釋放
//2)get返回非null,但是不等于自身憑證。由于有getset那一步,當兩個競爭線程都在這個過程中時,存在持有鎖的線程憑證不等于value,而value是稍慢那一步線程設置的value。
//
//2、解鎖前用憑證判斷鎖是否已經超時,如果沒有超時,直接刪除;如果超時,等著鎖自動過期就好,免得誤刪別人的鎖。但這種寫法同樣存在問題,由于線程調度的不確定性,判斷到刪除之間可能過去很久,并不是絕對意義上的正確解鎖。
//
//關于解鎖我只想到這么多,希望有幫助,歡迎拍磚多交流。
//綜上所述,lzc.java實現采用了非常簡單的方法,如上所述,即超時的情況下可能會出現誤釋放鎖的場景,所以使用的時候就需要合理設置超時時間了
redis.del(lockKey);
exclusiveOwnerThread = null;
}
}
讀到這里,這篇“基于Redis的SETNX操作怎么實現分布式鎖”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。