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); } } }
郭慕荣博客园