Redis實現可重入鎖


可重入鎖
可重入鎖是指一個鎖在被一個線程持有后,在該線程未釋放鎖前的任何時間內,只要再次訪問被該鎖鎖住的函數區都可以再次進入對應的鎖區域。可重入鎖有一個可重入度的概念,即每次重新進入一次該鎖的鎖住的區域都會遞增可重入度,每次退出一個該鎖鎖住的區域都會遞減可重入度,最終釋放全部鎖后,可重入度為0。
可重入問題
可重入鎖指的是可重復可遞歸調用的鎖,在外層使用鎖之后,在內層仍然可以使用,如果沒有可重入鎖的支持,在第二次嘗試獲得鎖時將會進入死鎖狀態。
這里有兩種解決方案:
①:客戶端在獲得鎖后保存value(擁有者標記),然后釋放鎖的時候將value和key同時傳過去。
②:利用ThreadLocal實現,獲取鎖后將Redis中的value保存在ThreadLocal中,同一線程再次嘗試獲取鎖的時候就先將 ThreadLocal 中的 值 與 Redis 的 value 比較,如果相同則表示這把鎖所以該線程,即實現可重入鎖。

示例一:

 1 @Slf4j
 2 @Component
 3 public class RedisDistributedLockImpl implements IRedisDistributedLock {
 4 
 5     /**
 6      * key前綴
 7      */
 8     public static final String PREFIX = "Lock:";
 9     /**
10      * 保存鎖的value
11      */
12     private ThreadLocal<String> threadLocal = new ThreadLocal<>();
13 
14     private static final Charset UTF8 = Charset.forName("UTF-8");
15     /**
16      * 釋放鎖腳本
17      */
18     private static final String UNLOCK_LUA;
19 
20     /*
21      * 釋放鎖腳本,原子操作
22      */
23     static {
24         StringBuilder sb = new StringBuilder();
25         sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
26         sb.append("then ");
27         sb.append("    return redis.call(\"del\",KEYS[1]) ");
28         sb.append("else ");
29         sb.append("    return 0 ");
30         sb.append("end ");
31         UNLOCK_LUA = sb.toString();
32     }
33 
34     @Autowired
35     private RedisTemplate redisTemplate;
36 
37     @Override
38     public boolean lock(String key, long requireTimeOut, long lockTimeOut) {
39         //可重入鎖判斷
40         String originValue = threadLocal.get();
41         if (!StringUtils.isBlank(originValue) && isReentrantLock(key, originValue)) {
42             return true;
43         }
44         String value = UUID.randomUUID().toString();
45         long end = System.currentTimeMillis() + requireTimeOut;
46         try {
47             while (System.currentTimeMillis() < end) {
48                 if (setNX(wrapLockKey(key), value, lockTimeOut)) {
49                     threadLocal.set(value);
50                     return true;
51                 }
52             }
53         } catch (Exception e) {
54             e.printStackTrace();
55         }
56         return false;
57     }
58 
59     private boolean setNX(String key, String value, long expire) {
60         List<String> keyList = new ArrayList<>();
61         keyList.add(key);
62         return (boolean) redisTemplate.execute((RedisCallback<Boolean>) connection -> {
63             Boolean result = connection
64                     .set(key.getBytes(UTF8),
65                             value.getBytes(UTF8),
66                             Expiration.milliseconds(expire),
67                             RedisStringCommands.SetOption.SET_IF_ABSENT);
68             return result;
69         });
70 
71     }
72 
73     /**
74      * 是否為重入鎖
75      */
76     private boolean isReentrantLock(String key, String originValue) {
77         String v = (String) redisTemplate.opsForValue().get(key);
78         return v != null && originValue.equals(v);
79     }
80 
81     @Override
82     public boolean release(String key) {
83         String originValue = threadLocal.get();
84         if (StringUtils.isBlank(originValue)) {
85             return false;
86         }
87         return (boolean) redisTemplate.execute((RedisCallback<Boolean>) connection -> {
88             return connection
89                     .eval(UNLOCK_LUA.getBytes(UTF8), ReturnType.BOOLEAN, 1, wrapLockKey(key).getBytes(UTF8),
90                             originValue.getBytes(UTF8));
91         });
92     }
93 
94 
95     private String wrapLockKey(String key) {
96         return PREFIX + key;
97     }
98 
99 }

 示例二:

@Component
@Slf4j
public class RedisLockUtils {
    //鎖超時時間1分鍾
    private static final long LOCK_TIME_OUT = 60000L;

    //加鎖阻塞等待時間
    private static final long THREAD_SLEEP_TIME = 500L;

    @Resource
    private RedisTemplate redisTemplate;
    /**
     * 本地線程池
     */
    private static final ThreadLocal<Map<String,Boolean>> doubleLock = new ThreadLocal<Map<String,Boolean>>(){
        @Override
        protected Map<String,Boolean> initialValue(){
            log.info("初始化成功");
            return new HashMap<>();
        }
    };

    public Boolean lock(String key,Long timeOut){
        String lockKey = RedisKeyConstant.getRedisKey(RedisKeyConstant.Lock, key);
        log.info("獲取redis鎖開始,lockKey = {}",lockKey);
        try{
            while (timeOut >= 0){
                String expires = String.valueOf(System.currentTimeMillis() + LOCK_TIME_OUT);
                //如果鍵不存在則新增,存在則不改變已經有的值。
                boolean isSuccess = redisTemplate.opsForValue().setIfAbsent(lockKey,expires);
                if(isSuccess){
                    doubleLock.get().put(lockKey,true);
                    log.info("獲取redis鎖成功,lockKey = {}",lockKey);
                    return true;
                }else{
                    //獲取key鍵對應的值
                    Object expiresObj = redisTemplate.opsForValue().get(lockKey);
                    if(expiresObj != null) {
                        String expiresObjStr = (String)expiresObj;
                        if(Long.valueOf(expiresObjStr) < System.currentTimeMillis() ){
                            //獲取原來key鍵對應的值並重新賦新值
                            Object expiresOldObj = redisTemplate.opsForValue().getAndSet(lockKey,expires);
                            String expiresOldObjStr = (String)expiresOldObj;
                            if(expiresOldObj != null && expiresObjStr.equals(expiresOldObjStr)){
                                doubleLock.get().put(lockKey,true);
                                log.info("獲取redis鎖成功,lockKey = {}",lockKey);
                                return true;
                            }
                        }
                    }
                }
                timeOut -= THREAD_SLEEP_TIME;
                Thread.sleep(THREAD_SLEEP_TIME);
            }
        }catch (Exception e){
            log.info("獲取redis鎖失敗,lockKey = {},exception={}",lockKey,e);
        }finally {
            log.info("獲取redis鎖結束,lockKey = {}",lockKey);
        }
        log.info("獲取redis鎖失敗,lockKey = {}",lockKey);
        return false;
    }

    public void releaseLock(String key){
        String lockKey = RedisKeyConstant.getRedisKey(RedisKeyConstant.Lock, key);
        log.info("刪除redis鎖開始,lockKey = {}",lockKey);
        try {
            boolean isSuccess = redisTemplate.delete(lockKey);
            if(isSuccess){
                log.info("刪除redis鎖成功,lockKey = {}",lockKey);
                return;
            }
            log.info("沒有需要刪除的redis鎖,lockKey = {}",lockKey);
        }catch (Exception e){
            log.info("刪除redis鎖失敗,lockKey = {},exception = {}",lockKey,e);
        }finally {
            doubleLock.get().remove(lockKey);
            log.info("刪除redis鎖結束,lockKey = {}",lockKey);
        }
    }

}

 


免責聲明!

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



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