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取消续约任务,如果在解锁过程之中发生异常,就记录异常,如果解锁失败(当前线程未持有锁)就抛出异常,解锁成功就标记操作成功。

相关推荐
xrkhy3 小时前
redis-shake的使用windows版本
数据库·windows·redis
老鼠拧刀满街找猫5 小时前
Windows Docker部署Redis一主两从+哨兵完整实战(含静态IP方案)
windows·redis·docker
零域码客6 小时前
Redis 双刃剑:缓存与分布式锁的本质区别
redis·分布式·缓存·并发控制·后端架构
Escalating_xu6 小时前
【Linux】线程概念与控制:从地址空间、LWP 到 pthread_create、join、detach 与 NPTL
java·linux·redis
InfinitePlus7 小时前
docker redis部署
redis·docker·容器
会编程的土豆9 小时前
12306 春运抢票:数据一致性怎么保证
redis·消息队列·go·高并发
ltl16 小时前
Redis 紧凑编码:listpack、quicklist 与 intset
redis
野生技术架构师1 天前
Redis 和 MySQL 如何保证数据一致性?先更新数据库还是先删缓存,延迟双删、MQ、Canal 一次讲透
数据库·redis·缓存
Devin~Y1 天前
从内容社区到AI智能客服:Spring Boot + Spring Cloud + Spring AI 全栈实战面试拆解
java·spring boot·redis·elasticsearch·spring cloud·kafka·mybatis
2602_959960921 天前
电商大厂Java面试:从Spring Boot、JPA、微服务到Redis、Kafka、Spring Security与监控,谢飞机的爆笑三轮问答
java·jvm·spring boot·redis·面试题