Redis分布式锁常见坑点分析( 三 )

结果如下:

Redis分布式锁常见坑点分析

文章插图

可以看到,线程 A 先申请到锁,线程 B 后申请锁,结果线程 B 申请加锁失败 。
4.3 锁得可重入性实践当前线程加锁成功后,在线程执行中继续申请同一把锁,代码如下:
@Testpublic void redisLockReEntryTest() {    String key = "test";    try {        log.info("---申请加锁");        if (redisLock.lock(key, 10)) {            // 模拟任务执行15秒            log.info("---加锁第一次成功");            if (redisLock.lock(key, 10)) {                // 模拟任务执行15秒                log.info("---加锁第二次成功");                Thread.sleep(15000);                log.info("---加锁第二次执行完毕");            } else {                log.info("---加锁第二次失败");            }            Thread.sleep(15000);            log.info("---加锁第一次执行完毕");        } else {            log.info("---加锁第一次失败");        }    } catch (Exception e) {        log.error(e.getMessage(), e);    } finally {        redisLock.unLock(key);    }}结果如下:

Redis分布式锁常见坑点分析

文章插图
4.4 加锁逻辑讲解直接贴出本文最核心 RedisLock 类全部代码:
@Slf4j@Componentpublic class RedisLock {    @Autowired    public RedisTemplate redisTemplate;    /**     * 默认锁过期时间20秒     */    public static final Integer DEFAULT_TIME_OUT = 30;    /**     * 保存线程id-ThreadLocal     */    private ThreadLocal<String> stringThreadLocal = new ThreadLocal<>();    /**     * 保存定时任务(watch-dog)-ThreadLocal     */    private ThreadLocal<ExecutorService> executorServiceThreadLocal = new ThreadLocal<>();    /**     * 加锁,不指定过期时间     *     * @param key key名称     * @return boolean     */    public boolean lock(String key) {        return lock(key, null);    }    /**     * 加锁     *     * @param key     key名称     * @param timeout 过期时间     * @return boolean     */    public boolean lock(String key, Integer timeout) {        Integer timeoutTmp;        if (timeout == null) {            timeoutTmp = DEFAULT_TIME_OUT;        } else {            timeoutTmp = timeout;        }        String nanoId;        if (stringThreadLocal.get() != null) {            nanoId = stringThreadLocal.get();        } else {            nanoId = IdUtil.nanoId();            stringThreadLocal.set(nanoId);        }        RedisScript<Long> redisScript = new DefaultRedisScript<>(buildLuaLockScript(), Long.class);        Long execute = (Long) redisTemplate.execute(redisScript, Collections.singletonList(key), nanoId, timeoutTmp);        boolean flag = execute != null && execute == 1;        if (flag) {            ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();            executorServiceThreadLocal.set(scheduledExecutorService);            scheduledExecutorService.scheduleWithFixedDelay(() -> {                RedisScript<Long> renewRedisScript = new DefaultRedisScript<>(buildLuaRenewScript(), Long.class);                Long result = (Long) redisTemplate.execute(renewRedisScript, Collections.singletonList(key), nanoId, timeoutTmp);                if (result != null && result == 2) {                    ThreadUtil.shutdownAndAwAItTermination(scheduledExecutorService);                }            }, 0, timeoutTmp / 3, TimeUnit.SECONDS);        }        return flag;    }    /**     * 释放锁     *     * @param key key名称     * @return boolean     */    public boolean unLock(final String key) {        String nanoId = stringThreadLocal.get();        RedisScript<Long> redisScript = new DefaultRedisScript<>(buildLuaUnLockScript(), Long.class);        Long execute = (Long) redisTemplate.execute(redisScript, Collections.singletonList(key), nanoId);        boolean flag = execute != null && execute == 1;        if (flag) {            if (executorServiceThreadLocal.get() != null) {                ThreadUtil.shutdownAndAwaitTermination(executorServiceThreadLocal.get());            }        }        return flag;    }    private String buildLuaLockScript() {        return """                local key = KEYS[1]                local value = ARGV[1]                local time_out = ARGV[2]                local result = redis.call('get', key)                if result == value then                    return 1;                end                local lock_result = redis.call('setnx', key, value)                if tonumber(lock_result) == 1 then                    redis.call('expire', key, time_out)                    return 1;                else                    return 0;                end                """;    }    private String buildLuaUnLockScript() {        return """                local key = KEYS[1]                local value = ARGV[1]                local result = redis.call('get', key)                if result ~= value then                    return 0;                else                    redis.call('del', key)                end                return 1;                """;    }    private String buildLuaRenewScript() {        return """                local key = KEYS[1]                local value = ARGV[1]                local timeout = ARGV[2]                local result = redis.call('get', key)                if result ~= value then                    return 2;                end                local ttl = redis.call('ttl', key)                if tonumber(ttl) < tonumber(timeout) / 2 then                    redis.call('expire', key, timeout)                    return 1;                else                    return 0;                end                """;    }}


推荐阅读