Redis实现分布式锁方案整理

分布式锁一般有三种实现方式:1. 数据库乐观锁;2. 基于Redis的分布式锁;3. 基于ZooKeeper的分布式锁。
和其他两种锁比起来,Redis实现的分布式锁性能更高,对于高并发场景更加支持,ZK实现的分布式锁是强一致性的,也就是说是非常安全的,但是性能有所下降
Redis是AP模型,ZK是CP模型

可靠性

首先,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:

  1. 互斥性。在任意时刻,只有一个客户端能持有锁。——锁的基础特性
  2. 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。——锁的可靠性
  3. 具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。——主要指failover时,主从切换会不会丢锁的问题,不丢锁的前提是强一致
  4. 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

代码实现

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <optional>true</optional>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.redisson</groupId>
  <artifactId>redisson</artifactId>
  <version>3.16.3</version>
</dependency>
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>fastjson</artifactId>
  <version>1.2.75</version>
</dependency>
<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.13.2</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-pool2</artifactId>
</dependency>

针对于实现Redis分布式锁方案,研究各种文章和以及部分源码,实现了三种较可靠的实现方式,这里先引入主要借鉴的文章
https://zhuanlan.zhihu.com/p/53838899
https://zhuanlan.zhihu.com/p/150602212
https://www.cnblogs.com/fightingtong/p/12360222.html
直接上方案代码吧,接口结构这个没啥关系

简易分布式非重入锁

/**
 * @author Franky
 * @title: SimpleDistributedLock
 * @description: 简单Redis实现分布式锁,非重入锁,不保证failover时锁状态正常
 * @date 2021年10月26日 11:12
 */
@Component
public class SimpleRedisDistributedLock implements DistributedLock {
    // 永远等待
    private static final Long PERMANENT_WAIT_TIME = -1L;
    // 立即返回
    private static final Long IMMEDIATELY_RETURN = 0L;

    private ThreadLocal<String> localId = ThreadLocal.withInitial(() -> String.format("%s-%s", Thread.currentThread().getName(), UUID.randomUUID().toString()));

    @Autowired
    private ValueOperations stringOperator;
    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public Boolean lock(String key) throws InterruptedException {
        return lock(key, 30L, TimeUnit.SECONDS);
    }

    @Override
    public Boolean lock(String key, Long expireTime, TimeUnit unit) throws InterruptedException {
        return lock(key, expireTime, PERMANENT_WAIT_TIME, unit);
    }

    @Override
    public Boolean lockNow(String key, Long expireTime, TimeUnit unit) {
        Boolean success = false;
        try {
            success = lock(key, expireTime, IMMEDIATELY_RETURN, unit);
        } catch (InterruptedException e) {
            // 吃掉可能出现的异常
            e.printStackTrace();
        }
        return success;
    }

    @Override
    public Boolean lock(final String key, final Long expireTime, Long waitTime, final TimeUnit unit) throws InterruptedException {
        Boolean success = true;
        Long endTime = 0L;
        if (waitTime < 0)
            waitTime = PERMANENT_WAIT_TIME;
        else if (0 > waitTime)
            endTime = System.currentTimeMillis() + unit.toMillis(waitTime);
        do {
            // 阻塞获取lock,每一百毫秒请求一次
            if (!success) TimeUnit.MILLISECONDS.sleep(100L);
            // 需要原子性操作
            success = stringOperator.setIfAbsent(key, getRequestId(), expireTime, unit);
            // 阻塞/未到等待时间的情况下,获取失败重复获取
        }while (!success && (waitTime == PERMANENT_WAIT_TIME || endTime > System.currentTimeMillis()));

        return success;
    }

    @Override
    public Boolean unlock(String key) {
        try {
            // lua脚本使redis原子性操作
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
//        ClassPathResource resource = new ClassPathResource(script); 可以使用外置脚本
            Long result = (Long)redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList(key), getRequestId());
            return 1 == result;
        }finally {
            this.localId.remove();
        }
    }

    @Override
    public void unlock(String key, Consumer<? super Object> action) {
        Boolean unlock = unlock(key);
        if (null != action)
            action.accept(unlock);
    }

    /**
     * 返回唯一requestId
     * @return
     */
    private String getRequestId() {
        return this.localId.get();
    }
}

对于这里的Lock和unLock给了几个生产中常用的重载方法
这部分内容主要是参考了https://zhuanlan.zhihu.com/p/53838899这篇文章得到的,写的非常详细

分布式重入锁

/**
 * @author Franky
 * @title: ReEnRedisDistributedLock
 * @description: 自定义可重入锁,不保证failover时锁状态正常
 * @date 2021年10月27日 16:24
 */
@Component
public class ReEnRedisDistributedLock implements DistributedLock {
    // 永远等待
    private static final Long PERMANENT_WAIT_TIME = -1L;
    // 立即返回
    private static final Long IMMEDIATELY_RETURN = 0L;

    private ThreadLocal<String> localId = ThreadLocal.withInitial(() -> String.format("%s-%s", Thread.currentThread().getName(), UUID.randomUUID().toString()));

    private ThreadLocal<Long> expireTime = new ThreadLocal<>();

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public Boolean lock(String key) throws InterruptedException {
        return lock(key, 30L, TimeUnit.SECONDS);
    }

    @Override
    public Boolean lock(String key, Long expireTime, TimeUnit unit) throws InterruptedException {
        return lock(key, expireTime, PERMANENT_WAIT_TIME, unit);
    }

    @Override
    public Boolean lockNow(String key, Long expireTime, TimeUnit unit) throws InterruptedException {
        return lock(key, expireTime, IMMEDIATELY_RETURN, unit);
    }

    @Override
    public Boolean lock(String key, Long expireTime, Long waitTime, TimeUnit unit) throws InterruptedException {
        boolean NonBlock = true;
        boolean permanent = false;
        this.expireTime.set(unit.toMillis(expireTime));
        if (waitTime < 0)
            permanent = true;
        Long current = System.currentTimeMillis();
        // lua脚本 可重入锁
        String script = "if (redis.call('exists', KEYS[1]) == 0) then " +
                        "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
                        "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                        "return nil; " +
                        "end; " +
                        "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                        "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
                        "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                        "return nil; " +
                        "end; " +
                        "return redis.call('pttl', KEYS[1]);";
        do {
            // 阻塞获取lock,每一百毫秒请求一次
            if (!NonBlock) TimeUnit.MILLISECONDS.sleep(100L);
            // 返回null 表示成功获取锁
            Object result = redisTemplate.execute(new DefaultRedisScript(script, Long.class), Arrays.asList(key), unit.toMillis(expireTime), getRequestId());
            if (null == result) return true;
            NonBlock = false;
            waitTime -= System.currentTimeMillis() - current;
            current = System.currentTimeMillis();
            // 阻塞/未到等待时间的情况下,获取失败重复获取
        }while (permanent || waitTime > 0);
        return false;
    }

    @Override
    public Boolean unlock(String key) {
        Object result = unlock(key, Object.class);
        return null != result;
    }

    @Override
    public void unlock(String key, Consumer<? super Object> action) {
        Object result = unlock(key, Object.class);
        if (null != action)
            action.accept(result);
    }

    /**
     * 返回唯一requestId
     * @return
     */
    private String getRequestId() {
        return this.localId.get();
    }

    /**
     * 对指定key进行解锁
     * @param key key
     * @param tClass 标识返回值类型
     * @param <T> 标识返回值类型
     * @returnnull:解锁失败; 0:解重入锁一次成功; 1:解锁成功
     */
    public<T> T unlock(String key, Class<T> tClass) {
        try {

            // lua脚本使redis原子性操作
            String script = "if (redis.call('hexists', KEYS[1], ARGV[2]) == 0) then " +
                    "return nil;" +
                    "end; " +
                    "local counter = redis.call('hincrby', KEYS[1], ARGV[2], -1); " +
                    "if (counter > 0) then " +
                    "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                    "return 0; " +
                    "else " +
                    "redis.call('del', KEYS[1]); " +
                    "return 1; " +
                    "end; " +
                    "return nil;";
            //        ClassPathResource resource = new ClassPathResource(script); 可以使用外置脚本
            Object result = redisTemplate.execute(new DefaultRedisScript(script, Long.class), Arrays.asList(key), this.expireTime.get(), getRequestId());
            return (T)result;
        }finally {
            // 防止内存泄漏
            this.expireTime.remove();
            this.localId.remove();
        }
    }
}

这部分内容主要是查看了Redisson源码后简化了部分,自定义了一个可重入锁,因为Redisson中会优先保证锁信息被其他可能存在的从节点同步后才会返回生成,虽然这个保证了锁的容错性,但是也降低了性能,这里仿照写了一个可重入锁,不保证容错性,但是在实际生产中可以通过补偿逻辑去保证容错性。

Redisson实现分布式锁

/**
 * @author Franky
 * @title: RedissonDistributedLock
 * @description: 通过 redisson client 实现分布式可重入锁,保证failover时锁状态正常
 * @date 2021年10月27日 11:11
 */
@Component
public class RedissonDistributedLock implements DistributedLock {

    private ThreadLocal<RLock> reEntranceLock = new ThreadLocal();

    @Autowired
    private RedissonClient redisson;

    @Override
    public Boolean lock(String key) throws InterruptedException {
        this.getRLock(key).lock();
        return true;
    }

    @Override
    public Boolean lock(String key, Long expireTime, TimeUnit unit) throws InterruptedException {
        this.getRLock(key).lock(expireTime, unit);
        return true;
    }

    @Override
    public Boolean lockNow(String key, Long expireTime, TimeUnit unit) throws InterruptedException {
        return this.lock(key, expireTime, 0L, unit);
    }

    @Override
    public Boolean lock(String key, Long expireTime, Long waitTime, TimeUnit unit) throws InterruptedException {
        return this.getRLock(key).tryLock(waitTime, expireTime, unit);
    }

    @Override
    public Boolean unlock(String key) {
        try {
            this.getRLock(key).unlock();
            return true;
        }finally {
            this.reEntranceLock.remove();
        }
    }

    @Override
    public void unlock(String key, Consumer<? super Object> action) {
        unlock(key);
        action.accept(true);
    }

    private RLock getRLock(String key) {
        if (null == this.reEntranceLock.get()) {
            // To increase reliability during failover, all operations wait for propagation to all Redis slaves.
            this.reEntranceLock.set(this.redisson.getLock(key));
        }
        return this.reEntranceLock.get();
    }
}

使用Redisson实现分布式锁,基本具体上面提到的四个特性,并且是可重入的,而且定义锁非常简单,简化了开发,但是问题么,上面也提到了

Redisson锁的原理推荐:https://zhuanlan.zhihu.com/p/150602212

Redisson基本流程

![image.png](https://img-blog.csdnimg.cn/img_convert/4f735aea7a115a9855b4c8438446d90d.png#clientId=ub5b6bff3-48f7-4&from=paste&id=u782e7d2d&margin=[object Object]&name=image.png&originHeight=860&originWidth=1440&originalType=url&ratio=1&size=556208&status=done&style=none&taskId=u86ac23cf-678a-49b0-aeec-4dc659d53f3)
借图一张,肯定不会和上面的原理重复,主要是梳理整个流程,就是将流程图汇总的内容通过源码来梳理一下

其实上面这个流程图指的是RedissonClient.lcok()这个无参的方法,主要是体现到两点,如果没有设置锁过期时间,那么Redisson会默认一个过期时间,默认值是30Second,当然这个值也是可以设置的,如:

    @Bean("redissonClient")
    public RedissonClient getRedisson() {
        Config config = new Config();
        // 默认expire 时间 redisson lock 128行
        config.setLockWatchdogTimeout(3000);
        config.useSingleServer()
                .setTimeout(1000000)
                .setAddress(String.format("redis://%s:%s", this.host, this.port))
                .setPassword(this.password);
        return Redisson.create(config);
    }

第二点就是客户端B不断尝试加锁,这个是因为没有指定等待时间,如果指定等待时间,那么会在等待时间到后返回失败。

无参lock()方法

根据流程图,还是先从无参的lock出发,然后再看一下其他流程
RedissonLock类中,有这样的代码

    @Override
    public void lock() {
        try {
            // 默认过期时间是-1,那么将走WatchDog逻辑,默认过期时间并且自动延长锁时间
            lock(-1, null, false);
        } catch (InterruptedException e) {
            throw new IllegalStateException();
        }
    }

    @Override
    public void lock(long leaseTime, TimeUnit unit) {
        try {
            lock(leaseTime, unit, false);
        } catch (InterruptedException e) {
            throw new IllegalStateException();
        }
    }


    @Override
    public void lockInterruptibly() throws InterruptedException {
        lock(-1, null, true);
    }

    @Override
    public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {
        lock(leaseTime, unit, true);
    }
	/**
    	针对于无等待时间获取锁方式都会走这个方法
    */
    private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
        long threadId = Thread.currentThread().getId();
        // 所有获取锁逻辑都使用tryAcquire
        Long ttl = tryAcquire(-1, leaseTime, unit, threadId);
        // lock acquired
        if (ttl == null) {
            return;
        }
		// 获取锁失败,走下面逻辑, interruptibly为ture,代表当线程被中断时,中断获取锁逻辑
        RFuture<RedissonLockEntry> future = subscribe(threadId);
        if (interruptibly) {
            commandExecutor.syncSubscriptionInterrupted(future);
        } else {
            commandExecutor.syncSubscription(future);
        }

        try {
            // 死循环获取锁
            while (true) {
                ttl = tryAcquire(-1, leaseTime, unit, threadId);
                // lock acquired
                if (ttl == null) {
                    break;
                }

                // waiting for message
                if (ttl >= 0) {
                    try {
                        future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                    } catch (InterruptedException e) {
                        if (interruptibly) {
                            throw e;
                        }
                        future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                    }
                } else {
                    if (interruptibly) {
                        future.getNow().getLatch().acquire();
                    } else {
                        future.getNow().getLatch().acquireUninterruptibly();
                    }
                }
            }
        } finally {
            unsubscribe(future, threadId);
        }
//        get(lockAsync(leaseTime, unit));
    }

这里有个疑问,关于Redisson第一次获取锁失败后,会进行订阅,应该和它的尝试次数是有关系的,可以中断线程,待以后确定
tryAcquire方法后,会进入这个方法

    private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
        RFuture<Long> ttlRemainingFuture;
        if (leaseTime != -1) {
            ttlRemainingFuture = tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
        } else {
            // 当锁的过期时间未设置时,默认internalLockLeaseTime这个时间为过期时间
            ttlRemainingFuture = tryLockInnerAsync(waitTime, internalLockLeaseTime,
                    TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
        }
        // 响应完成后执行
        ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
            // 报错后返回
            if (e != null) {
                return;
            }

            // lock acquired
            if (ttlRemaining == null) {
                if (leaseTime != -1) {
                    internalLockLeaseTime = unit.toMillis(leaseTime);
                } else {
                    // 对于为设置过期时间的锁,watchDog自动延长锁的过期时间
                    scheduleExpirationRenewal(threadId);
                }
            }
        });
        return ttlRemainingFuture;
    }
// 具体加锁逻辑,下面的lua表达式在https://zhuanlan.zhihu.com/p/150602212文章中有很好的解释
<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
        return evalWriteAsync(getRawName(), LongCodec.INSTANCE, command,
                "if (redis.call('exists', KEYS[1]) == 0) then " +
                        "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
                        "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                        "return nil; " +
                        "end; " +
                        "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                        "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
                        "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                        "return nil; " +
                        "end; " +
                        "return redis.call('pttl', KEYS[1]);",
                Collections.singletonList(getRawName()), unit.toMillis(leaseTime), getLockName(threadId));
    }
// 自动延长锁的过期时间  
protected void scheduleExpirationRenewal(long threadId) {
        ExpirationEntry entry = new ExpirationEntry();
    // 这是一个关于可重入锁的逻辑,只要没有将全部的锁unlock掉,下面的延迟刷新过期时间任务就会一直有效,后面有unlock的逻辑代码
        ExpirationEntry oldEntry = EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry);
        if (oldEntry != null) {
            oldEntry.addThreadId(threadId);
        } else {
            entry.addThreadId(threadId);
            try {
                // 刷新过期时间
                renewExpiration();
            } finally {
                if (Thread.currentThread().isInterrupted()) {
                    cancelExpirationRenewal(threadId);
                }
            }
        }
    }
    private void renewExpiration() {
        ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());
        if (ee == null) {
            return;
        }
        // 刷新锁的过期时间主要是发生在这里
        Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
            @Override
            public void run(Timeout timeout) throws Exception {
                ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());
                if (ent == null) {
                    return;
                }
                Long threadId = ent.getFirstThreadId();
                if (threadId == null) {
                    return;
                }
                // 看下面的方法
                RFuture<Boolean> future = renewExpirationAsync(threadId);
                future.onComplete((res, e) -> {
                    if (e != null) {
                        log.error("Can't update lock " + getRawName() + " expiration", e);
                        EXPIRATION_RENEWAL_MAP.remove(getEntryName());
                        return;
                    }
                    // 成功后递归
                    if (res) {
                        // reschedule itself
                        renewExpiration();
                    } else {
                        cancelExpirationRenewal(null);
                    }
                });
            }
        }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
        // 这个在后面取消锁的自动延长时间任务时使用
        ee.setTimeout(task);
    }
	// 这里直接lua表达式,就是判断指定的锁是否存在,若存在则刷新过期时间返回1,其他返回0,刷新的过期时间依然是默认时间
    protected RFuture<Boolean> renewExpirationAsync(long threadId) {
        return evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                        "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                        "return 1; " +
                        "end; " +
                        "return 0;",
                Collections.singletonList(getRawName()),
                internalLockLeaseTime, getLockName(threadId));
    }

有参tryLock()方法

这里类似的逻辑,但是不会走watchDog逻辑,因为在这里的时候就被限制住了
![image.png](https://img-blog.csdnimg.cn/img_convert/c39200253b085dcdf29114ade83c6aba.png#clientId=ub5b6bff3-48f7-4&from=paste&height=465&id=u94cfb69a&margin=[object Object]&name=image.png&originHeight=930&originWidth=2551&originalType=binary&ratio=1&size=166542&status=done&style=none&taskId=u160bc33f-f969-4ade-a878-0eb5aadbde1&width=1275.5)
这个方法和lock方法主要的差距在这里,其他内容都一样

 @Override
    public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
        long time = unit.toMillis(waitTime);
        long current = System.currentTimeMillis();
        long threadId = Thread.currentThread().getId();
        // 尝试获取锁
        Long ttl = tryAcquire(waitTime, leaseTime, unit, threadId);
        // lock acquired
        if (ttl == null) {
            return true;
        }
        // 判断等待时间是否到了,到了就返回false,获取失败
        time -= System.currentTimeMillis() - current;
        if (time <= 0) {
            acquireFailed(waitTime, unit, threadId);
            return false;
        }
        
        current = System.currentTimeMillis();
        RFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);
        if (!subscribeFuture.await(time, TimeUnit.MILLISECONDS)) {
            if (!subscribeFuture.cancel(false)) {
                subscribeFuture.onComplete((res, e) -> {
                    if (e == null) {
                        unsubscribe(subscribeFuture, threadId);
                    }
                });
            }
            acquireFailed(waitTime, unit, threadId);
            return false;
        }

        try {
            time -= System.currentTimeMillis() - current;
            if (time <= 0) {
                acquireFailed(waitTime, unit, threadId);
                return false;
            }
        	// 下面依然是死循环获取锁,但是在等待时间到后,会自动返回失败,而不是一直等待下去
            while (true) {
                long currentTime = System.currentTimeMillis();
                ttl = tryAcquire(waitTime, leaseTime, unit, threadId);
                // lock acquired
                if (ttl == null) {
                    return true;
                }

                time -= System.currentTimeMillis() - currentTime;
                if (time <= 0) {
                    acquireFailed(waitTime, unit, threadId);
                    return false;
                }

                // waiting for message
                currentTime = System.currentTimeMillis();
                if (ttl >= 0 && ttl < time) {
                    subscribeFuture.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                } else {
                    subscribeFuture.getNow().getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
                }

                time -= System.currentTimeMillis() - currentTime;
                if (time <= 0) {
                    acquireFailed(waitTime, unit, threadId);
                    return false;
                }
            }
        } finally {
            unsubscribe(subscribeFuture, threadId);
        }
//        return get(tryLockAsync(waitTime, leaseTime, unit));
    }

unlock方法

    @Override
    public RFuture<Void> unlockAsync(long threadId) {
        RPromise<Void> result = new RedissonPromise<>();
        // 解锁的lua逻辑
        RFuture<Boolean> future = unlockInnerAsync(threadId);

        future.onComplete((opStatus, e) -> {
            // 取消上面提到的自动刷新过期时间任务
            cancelExpirationRenewal(threadId);
			// 其他异常
            if (e != null) {
                result.tryFailure(e);
                return;
            }
			// 锁不存在时,发生这个异常
            if (opStatus == null) {
                IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: "
                        + id + " thread-id: " + threadId);
                result.tryFailure(cause);
                return;
            }

            result.trySuccess(null);
        });

        return result;
    }

    protected void cancelExpirationRenewal(Long threadId) {
        ExpirationEntry task = EXPIRATION_RENEWAL_MAP.get(getEntryName());
        if (task == null) {
            return;
        }
        
        if (threadId != null) {
            task.removeThreadId(threadId);
        }
		// 这里判断了一下是否所有锁都unlock了,这是针对可重入锁,如果没有,不取消自动刷新过期时间任务
        if (threadId == null || task.hasNoThreads()) {
            Timeout timeout = task.getTimeout();
            if (timeout != null) {
                timeout.cancel();
            }
            EXPIRATION_RENEWAL_MAP.remove(getEntryName());
        }
    }
	// unlock逻辑
	// 当锁不存在时,返回null
	// 将锁的次数减1,查看锁是否全部unlock,如果没有刷新过期时间,
	// 全部unlock则删除锁并返回1
    protected RFuture<Boolean> unlockInnerAsync(long threadId) {
        return evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
                        "return nil;" +
                        "end; " +
                        "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
                        "if (counter > 0) then " +
                        "redis.call('pexpire', KEYS[1], ARGV[2]); " +
                        "return 0; " +
                        "else " +
                        "redis.call('del', KEYS[1]); " +
                        "redis.call('publish', KEYS[2], ARGV[1]); " +
                        "return 1; " +
                        "end; " +
                        "return nil;",
                Arrays.asList(getRawName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId));
    }

关于Redisson的源码情况大概就到这里了,如有错误,烦请指正!!!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值