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

相关推荐
小杜-coding4 小时前
黑马点评day04(分布式锁-setnx)
java·spring boot·redis·分布式·spring·java-ee·mybatis
LLLLLindream6 小时前
Redis-商品缓存
数据库·redis·缓存
天上掉下来个程小白7 小时前
缓存套餐-01.Spring Cache介绍和常用注解
java·redis·spring·缓存·spring cache·苍穹外卖
一眼青苔7 小时前
如何在MySQL中实现类似Redis的PING命令的功能来检测连接状态?
数据库·redis·mysql
码码哈哈0.07 小时前
2025最新:3分钟使用Docker快速部署单节点Redis
redis·docker·eureka
奔驰的小野码7 小时前
SpringAI实现AI应用-使用redis持久化聊天记忆
java·数据库·人工智能·redis·spring
海绵波波10710 小时前
【高并发】Celery + Redis异步任务队列方案提高OCR任务时的并发
redis·celery
香吧香13 小时前
Redis 连接池耗尽的一次异常定位
redis·异常
CopyLower15 小时前
解决 Redis 缓存与数据库一致性问题的技术指南
数据库·redis·缓存
chunfeng—19 小时前
Redis相关命令详解与原理(一)
数据库·redis·缓存