redis 分布式锁详解

2021-12-07 23:16:53 浏览数 (1)

我们设置key的时候,将value设置为一个随机值r,并且存在当前线程ThreadLocal。当释放锁,也就是删除key的时候,不是直接删除,而是先判断该key对应的value是否等于先前存在当前线程的随机值,只有当前当前线程持有锁,才删除该key,由于每个客户端产生的随机值是不一样的,这样一来就不会误释放别的客户端申请的锁了

代码语言:txt复制
public class RedisLock {
代码语言:txt复制
    private static Logger logger = LoggerFactory.getLogger(RedisLock.class);
代码语言:txt复制
    private static OnecachePlugin oneCache;
代码语言:txt复制
    static {
代码语言:txt复制
        oneCache = SpringUtil.getBean(OnecachePlugin.class);
代码语言:txt复制
    }
代码语言:txt复制
    /**
代码语言:txt复制
     * 基础有效时间
     */
    private static final int BASE_VAILD_TIME = 10;
代码语言:txt复制
    /**
代码语言:txt复制
     * 锁的基本等待时间10s
     */
    private static final int BASE_WAIT_TIME = 4;
代码语言:txt复制
    /**
代码语言:txt复制
     * 随机数
     */
    private static Random random = new Random();
代码语言:txt复制
    private static ThreadLocal<Set<String>> threadLocal = new ThreadLocal<>();
代码语言:txt复制
    private static void currentThreadSleep() throws InterruptedException {
代码语言:txt复制
        Thread.sleep((long) (200), random.nextInt(5000));
代码语言:txt复制
    }
代码语言:txt复制
    public static boolean easyLock(String key) throws Exception {
代码语言:txt复制
        return easyLock(key, BASE_WAIT_TIME, BASE_VAILD_TIME);
代码语言:txt复制
    }
代码语言:txt复制
    //加锁
代码语言:txt复制
    public static boolean easyLock(String key, Integer waitTime, Integer expireTime) throws Exception {
代码语言:txt复制
        if (ObjectUtil.hasEmpty(waitTime)) {
代码语言:txt复制
            waitTime = BASE_WAIT_TIME;
代码语言:txt复制
        }
代码语言:txt复制
        if (ObjectUtil.hasEmpty(expireTime)) {
代码语言:txt复制
            expireTime = BASE_VAILD_TIME;
代码语言:txt复制
        }
代码语言:txt复制
        Long signTime = System.nanoTime();
代码语言:txt复制
        Long holdTime = TimeUnit.NANOSECONDS.convert(waitTime, TimeUnit.SECONDS);
代码语言:txt复制
        String state = UUIDGenerator.getUUID();
代码语言:txt复制
        while ((System.nanoTime() - signTime) < holdTime) {
代码语言:txt复制
            //从redis获取key 如果不存在,则将key存入redis
代码语言:txt复制
            RString rs = oneCache.getRString(key);
代码语言:txt复制
            //原子性加锁
代码语言:txt复制
            if (rs.setnx(state, Time.seconds(expireTime))) {
代码语言:txt复制
                logger.info(Thread.currentThread().getId() " 获取锁成功! key = " key);
代码语言:txt复制
                if(ObjectUtil.hasEmpty(threadLocal.get())){
代码语言:txt复制
                    threadLocal.set(Sets.newHashSet());
代码语言:txt复制
                }
代码语言:txt复制
                threadLocal.get().add(state);
代码语言:txt复制
                return true;
代码语言:txt复制
            } else {
代码语言:txt复制
                try {
代码语言:txt复制
                    currentThreadSleep();
代码语言:txt复制
                } catch (InterruptedException e) {
代码语言:txt复制
                    Thread.currentThread().interrupt();
代码语言:txt复制
                }
代码语言:txt复制
            }
代码语言:txt复制
        }
代码语言:txt复制
        throw new Exception( "系统正忙,稍后重试");
代码语言:txt复制
    }
代码语言:txt复制
    /**
代码语言:txt复制
     * redis简单分布式锁,业务执行完毕之后必须try finally 调用释放锁方法easyUnLock
     *
     * @param key 锁
     */
    public static void easyUnLock(String key) {
        logger.info(Thread.currentThread().getId() " 准备解锁! key = " key);
代码语言:txt复制
        //是否是当前线程持有锁,以免释放其他线程加的锁
代码语言:txt复制
        if (isHoldEasyLock(key)) {
代码语言:txt复制
            logger.info(Thread.currentThread().getId() " 开始解锁! key = " key);
代码语言:txt复制
            try {
代码语言:txt复制
                //从redis删除key
代码语言:txt复制
                RString rs = oneCache.getRString(key);
代码语言:txt复制
                String state = rs.get();
代码语言:txt复制
                rs.delete();
代码语言:txt复制
                threadLocal.get().remove(state);
代码语言:txt复制
                logger.info(Thread.currentThread().getId() " 解锁成功! key = " key);
代码语言:txt复制
            } catch (Exception e) {
代码语言:txt复制
                logger.info("RedisLockUtils easyLock解锁异常 ->"   e);
代码语言:txt复制
            }
代码语言:txt复制
        }
代码语言:txt复制
    }
代码语言:txt复制
    /**
代码语言:txt复制
     * redis简单分布式锁,判断线程是否持有锁
     */
    public static boolean isHoldEasyLock(String key) {
        if (ObjectUtil.hasEmpty(threadLocal.get())) {
            return false;
        }
        if (threadLocal.get().contains(oneCache.getRString(key).get())) {
            return true;
        } else {
            return false;
        }
    }
代码语言:txt复制
}

实际上,这样还是有一点问题,释放锁不是原子性,很有可能在查询完,redis也刚过期,再删除就把别的线程的锁释放了。

0 人点赞