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