可重入鎖
可重入鎖是指一個鎖在被一個線程持有后,在該線程未釋放鎖前的任何時間內,只要再次訪問被該鎖鎖住的函數區都可以再次進入對應的鎖區域。可重入鎖有一個可重入度的概念,即每次重新進入一次該鎖的鎖住的區域都會遞增可重入度,每次退出一個該鎖鎖住的區域都會遞減可重入度,最終釋放全部鎖后,可重入度為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); } } }