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);
        }
    }

}

 

posted @ 2021-03-07 11:06  郭慕荣  阅读(2697)  评论(0编辑  收藏  举报