zoukankan      html  css  js  c++  java
  • 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);
            }
        }
    
    }
    郭慕荣博客园
  • 相关阅读:
    javascript/jquery操作cookie
    更改IE/FireFox查看源代码的默认编辑器,比如notepad++
    javascript refresh page 几种页面刷新的方法
    C# Enum,Int,String的互相转换 枚举转换
    js中两个感叹号的作用
    JQuery操作iframe
    JQuery判断一个元素下面是否有内容或者有某个标签
    Meta标签详解
    五一放假回校,真爽
    ASP.NET错误处理(一)摘自MSDN
  • 原文地址:https://www.cnblogs.com/jelly12345/p/14493928.html
Copyright © 2011-2022 走看看