溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

基于Redis的SETNX操作怎么實(shí)現(xiàn)分布式鎖

發(fā)布時(shí)間:2022-01-15 16:51:09 來源:億速云 閱讀:179 作者:iii 欄目:大數(shù)據(jù)

本文小編為大家詳細(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è)資訊頻道。

向AI問一下細(xì)節(jié)

免責(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)容。

AI