本文小編為大家詳細(xì)介紹“基于redis的SETNX操作怎么實(shí)現(xiàn)分布式鎖”,內(nèi)容詳細(xì),步驟清晰,細(xì)節(jié)處理妥當(dāng),希望這篇“基于Redis的SETNX操作怎么實(shí)現(xiàn)分布式鎖”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學(xué)習(xí)新知識吧。
創(chuàng)新互聯(lián)服務(wù)項(xiàng)目包括淄川網(wǎng)站建設(shè)、淄川網(wǎng)站制作、淄川網(wǎng)頁制作以及淄川網(wǎng)絡(luò)營銷策劃等。多年來,我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢、行業(yè)經(jīng)驗(yàn)、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機(jī)構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,淄川網(wǎng)站推廣取得了明顯的社會效益與經(jīng)濟(jì)效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到淄川省份的部分城市,未來相信會繼續(xù)擴(kuò)大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!
import com.jd.jim.cli.Cluster;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
/**
*
* 基于Redis的SETNX操作實(shí)現(xiàn)的分布式鎖
*
* @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)檫@個和鎖超時(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) //表明已經(jīng)超時(shí)了,原來的線程可能可能出現(xiàn)意外未能及時(shí)釋放鎖 String oldLockVal=redis.getSet(lockKey,stringOfLockExpireTime); //為什么會有下面這個判斷呢?因?yàn)槎嗑€程情況下可能同時(shí)有多個線程在這一時(shí)刻發(fā)現(xiàn)鎖過期,那么就會同時(shí)執(zhí)行g(shù)etSet獲取鎖操作, //通過下面的比較,可以找到第一個執(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 忽略了一個地方。用del命令釋放鎖,如果線程A獲得鎖之后運(yùn)行太久,久到另已經(jīng)獲得的鎖失效了。 // 這時(shí)線程B進(jìn)來,取締了A上的鎖,線程B運(yùn)行到一半的時(shí)候,這時(shí)線程A也運(yùn)行完了,殺一個回馬槍把原本以為獲取到的鎖給del, // 實(shí)際上是B獲得的鎖,那么就會導(dǎo)致其他線程進(jìn)來競爭,而B還以為自己獨(dú)占鎖 //回復(fù)Ffadsfoadfjaodjfalkd:我也在思考這個問題,我覺得有一種寫法可以盡量避免。在鎖的時(shí)候,如果鎖住了,回傳超時(shí)時(shí)間,作為解鎖時(shí)候的憑證,解鎖時(shí)傳入鎖的鍵值和憑證。我思考的解鎖時(shí)候有兩種寫法: //1、解鎖前get一下鍵值的value,判斷是不是和自己的憑證一樣。但這樣存在一些問題: //1)get時(shí)返回null的可能,此時(shí)表示有別的線程拿到鎖并用完釋放 //2)get返回非null,但是不等于自身憑證。由于有g(shù)etset那一步,當(dāng)兩個競爭線程都在這個過程中時(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)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。
本文標(biāo)題:基于Redis的SETNX操作怎么實(shí)現(xiàn)分布式鎖
網(wǎng)站鏈接:http://weahome.cn/article/iegpds.html