Redisson锁源码详解

Redission获取锁

看下面一段代码,是Redisson获取锁最简单的实现方式

Java 复制代码
// 从Redisson客户端获取RLock对象
RLock lock = redissonClient.getLock(key);
// 使用RLock对象中的tryLock方法获取锁
boolean tryLock = lock.tryLock();
// 判断是否回去锁成功,!true = fase ,!false = true
if (!tryLock){
   return;
}
// 若获取锁成功则释放锁
lock.unlock();

补药小看这一段代码,Redisson提供了非常完善的锁实现的解决方案,让我们一点一点体会这段代码的美妙。

Redsson getLock()方法

我们点进getLock()方法,我们会看到这样一段代码

这段代码的意思就是new了一个RLock的实现类RedissonLock()返回了。其中name就是我们传入的key,也就是锁的key。那么connectionManager.getCommandExecutor()这是个什么东西呢?我们从字面意思可以得知这个是获取了一个命令执行器,那这个执行器是干啥的呢?

点进getCommandExecutor()方法

我们可以看到getCommandExecutor()方法是直接返回了CommandSyncService这个叫做命令同步服务的对象,那这个命令同步服务是干什么的呢? 点看进来看到CommandSyncService类继承了CommandAsyncService类,同时实现了CommandExecutor接口

看一下这里实现的方法,我们可以大概知道这个类是干什么的,CommandSyncService类是执行Redis同步命令的实现类。

Redisson tryLock()方法

我们点进去tryLock方法,我们可以发现tryLock方法有三个,分别是无参、两个参数和三个参数的。

tryLock()无参

我们先看无参的tryLock方法,首先进入tryLockAsync方法, 我们发现,tryLockAsync空参方法,其实就是调用了tryLockAsync有参方法,我们点进有参方法。 我们发现,在之前的tryLock方法中我们没有设置过期时间和等待时间,在这里都传入了-1,这里的-1的作用,我们后面再说。我们现在点进去tryAcquireOnceAsync方法看一下。 这治理我们可以看到,若是过期时间不为-1,则直接开始尝试获取锁,若过期时间为-1,则我们就设置默认的过期时间 30 * 1000 毫秒。在尝试锁成功之后调用scheduleExpirationRenewal方法安排锁的过期时间续期任务。 我们现在点进tryLockInnerAsync方法,看看Redisson是怎么获取锁的,这里我们选择RedissonLock的包。 我们看到这里其实就是使用lua脚本获取锁。

tryLock()三个参数

Java 复制代码
@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;
        }
        
        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));
    }

这段代码大体上的执行流程是 这里我们注意一下,如果过期时间是-1的话,我们是会走watchDog续约机制的。什么是watchDog续约机制,我下一章专门讲一下。 我们现在把关键的方法说一下

tryAcquire()

点进去,我们发现和之前的空参一样的,只是这里的参数都是我们之前设置的不再是-1了。 我们进入tryAcquireAsync方法 我们会发现,由于我们走的是自己设置的过期时间,所以我们这次会走if中的代码,而不是下面的代码 上面和下面代码的差别就在于,leaseTime和waitTime。

tryLock()两个个参数

点进方法我们就可以看到,其实还是调用了tryLock方法,只是将过期时间设置为了-1而已。

Redission释放锁

我们点进unLock()方法,选择RedissonLock包下的unLock()方法 这里其实就只有一段核心代码,就是get(unlockAsync(Thread.currentThread().getId())); 这段代码是什么意思呢?我们先看 unlockAsync()方法。 这段代码其实就是异步解锁,然后判断解锁是否成功的 点进unlockInnerAsync方法,选择Redisson包下的 我们可以看到这其实就是一段lua代码去redis中尝试解锁。 这段代码就是对lua脚本返回的结果进行处理判断的逻辑,在解锁完成之后调用cancelExpirationRenewal取消续约任务,如果在解锁过程之中发生异常,就记录异常,如果解锁失败(当前线程未持有锁)就抛出异常,解锁成功就标记操作成功。

相关推荐
Justice link3 小时前
部署redis cluster
数据库·redis·缓存
何似在人间5753 小时前
多级缓存模型设计
java·jvm·redis·缓存
smileNicky5 小时前
SpringBoot系列之集成Redisson实现布隆过滤器
java·spring boot·redis·布隆过滤器
張萠飛6 小时前
Redis哨兵模式下执行sentinel failover mymaster命令可能导致什么风险,如何避免
redis·bootstrap·sentinel
04Koi.7 小时前
Redis进阶--哨兵
数据库·redis·缓存
见未见过的风景9 小时前
使用 Redis + Redisson 分布式锁来生成全局唯一、线程安全的带日期前缀的流水号的完整实现。
数据库·redis·分布式
UniLCodes10 小时前
Redis 学习目标
redis·学习
振鹏Dong11 小时前
深入浅出Redis 缓存使用问题 | 长文分享
数据库·redis
Java&Develop11 小时前
redis 免安装版本 启动方法 windows 安装包
数据库·windows·redis