基於redis實現分布式鎖之不可重入導致死鎖的解決


假設程序a獲取到鎖之后需要調用程序b,但是程序b需要使用鎖,

但是這個時候程序a並沒有執行完程序所以不能釋放鎖,但是程序b獲取不到鎖就沒有辦法執行,因此就出現了死鎖

這樣可以使用可重入鎖解決(即判斷是自己的鎖則就可以再次獲取鎖)

existe 判斷鎖是否存在,hset 設置鎖, expire 設置鎖的到期時間  hincrby,設置鎖的重入次數的遞增

 

可重入鎖加鎖:

  1.判斷鎖是否被占用(exists lock),如果沒有(0)被占用則直接獲取鎖(hset lock uuid 1)

  2.如果鎖被占用了,則判斷當前鎖是否是當前線程占用了鎖(判斷是不是自己的鎖)(hexists lock uuid),如果是(1)則重入(hincrby lock uuid 1)

  3.如果返回0,則說明獲取鎖失敗

最后需要充值過期時間,因為程序a在執行期間很可能lock已經快過期,lock的剩余時間不足以等到b執行,因此最好重置下時間

lua腳本(使用lua腳本可以保證原子性)

  if redis.call('exists',KEYS[1])  == 0

  then 

    redis.call('hset',KEYS[1],ARVG[1],1)

    redis.call('expire',KEYS[1],ARVG[2])

    return 1

  elseif  redis.call('hexists',KEYS[1],ARVG[1])  == 1

  then 

    redis.call('hincrby', KEYS[1] ARVG[1], 1)

    redis.call('expire',KEYS[1],ARVG[2])

    retuen 1

  else 

    retuen 0

  end

lua腳本優化版

 

  if redis.call('exists',KEYS[1])  == 0  or redis.call('hexists',KEYS[1],ARVG[1])  == 1

  then 

    redis.call('hset',KEYS[1],ARVG[1],1)

    redis.call('expire',KEYS[1],ARVG[2])

    return 1

  else

    retuen 0

  end

 

KEYS:lock

ARGV: uuid 30

 

可重入鎖解鎖:

  lua腳本

  if redis,call('hexists' lock,uuid)  == 0

  then 

    return nil

  elseif redis.call('hincrby',lock,uuid , -1 )   == 0

  then 

    return redis.call('del' lock)

  else 

    return 0

  end


注:StringUtils.isBlank() :判斷字符串是否為空 Collerction.isEmpty判斷的的則是大小好像

 代碼實現

  新建代碼組件 

@Component
public class Distributedlock {

    @Autowired
    private StringRedisTemplate redisTemplate;
         public Boolean tryLock(String lockName,String uuid,Integer expire){   //鎖的名稱,uuid標識  到期時間
            String script = " if redis.call('exists',KEYS[1])  == 0  or redis.call('hexists',KEYS[1],ARVG[1])  == 1" +
                    "  then " +
                    "    redis.call('hset',KEYS[1],ARVG[1],1)" +
                    "    redis.call('expire',KEYS[1],ARVG[2]) " +
                    "       return 1 " +
                    " else " +
                    "retuen 0 " +
                    "end";
             Boolean flag = this.redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuid, expire.toString());
             if (!flag){
                 try {
                     Thread.sleep(50);
                     tryLock(lockName,uuid,expire);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
             return true;
         }

         public void unlock(String lockName,String uuid){

             String script = "lua腳本" +
                     "" +
                     "  if redis,call('hexists' KEYS[1],ARVG[1]) == 0" +
                     "  then " +
                     "    return nil" +
                     "  elseif redis.call('hincrby',KEYS[1],ARVG[1] , -1 )  == 0" +
                     "  then " +
                     "    return redis.call('del' KEYS[1])" +
                     "  else " +
                     "    return 0" +
                     "  end";
             Long flag = this.redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), uuid);
             //  DefaultRedisScript<>(需要執行的腳本,返回值類型)
             if (flag == null){
                 throw new RuntimeException("釋放的鎖不屬於你");
             }
         }

}

代碼調用

 public void testlock2(){
        String uuid = UUID.randomUUID().toString();
        Boolean lock = this.distributedlock.tryLock("lock", uuid, 30);
        if (lock){

        String num = this.redisTemplate.opsForValue().get("num");
        if (StringUtils.isBlank(num)){
            this.redisTemplate.opsForValue().set("num","1");
            return;
        }
        int i = Integer.parseInt(num);//把字符串轉化為數值
            this.redisTemplate.opsForValue().set("num",String.valueOf(++i));
            this.distributedlock.unlock("lock",uuid);
        }
    }

最后的解鎖應該定義在final里面,從而保證死鎖不會發生

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM