Redis-源码解析之RLock

RLock锁源码解析之lock上锁操作

入口:编写一个简单的案例

    /**
     * 可重入锁
     * 1.锁会自动续期,如果业务时间超长,运行期间Redisson会自动给锁重新添加30s,不用担心业务时间的问题,锁自动过期而导致业务发生问题
     * 2、加锁的业务只要完成执行,那么就不会给当前的锁续期,即使我们不去主动的释放锁,锁在默认30s之后也会自动的删除
     *
     * @return
     */
    @RequestMapping("/hello")
    @ResponseBody
    public String hello() {
        //获取锁
        RLock lock = redissonClient.getLock("lock");
        //进行加锁
        lock.lock();
        try {
            System.out.println("做业务逻辑的操作" + Thread.currentThread().getName());
            Thread.sleep(3000);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.out.println("释放锁成功。。" + Thread.currentThread().getName());
            //释放锁
            lock.unlock();
        }
        return "hello";
    }

lock.lock();获取锁,加锁。

来到RedissonLock类中:

    @Override
    public void lock() {
        try {
            //调用有参构造方法
            lock(-1, null, false);
        } catch (InterruptedException e) {
            throw new IllegalStateException();
        }
    }

调用lock(-1, null, false); 构造方法
当前类位置:RedissonLock

private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
        //获取当前线程的id
        long threadId = Thread.currentThread().getId();
        //获取锁,继续往下看源码
        Long ttl = tryAcquire(-1, leaseTime, unit, threadId);
        // lock acquired
        if (ttl == null) {
            return;
        }

        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));
    }

继续调用加锁的过程: Long ttl = tryAcquire(-1, leaseTime, unit, threadId);
当前类位置:RedissonLock

    private Long tryAcquire(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
        //继续执行tryAcquireAsync 方法
        return get(tryAcquireAsync(waitTime, leaseTime, unit, threadId));
    }

当前类位置:RedissonLock
继续执行:tryAcquireAsync(waitTime, leaseTime, unit, threadId)

    private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
        RFuture<Long> ttlRemainingFuture;
        if (leaseTime != -1) {
            //传入的参数不等于-1的情况
            ttlRemainingFuture = tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
        } else {
            // 等于-1的分支
            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 {
                    scheduleExpirationRenewal(threadId);
                }
            }
        });
        return ttlRemainingFuture;
    }

由于我们这没有进行设置key的过期时间,默认传入的值是-1
image.png
所以我们走的是等于-1的分支
image.png
执行的代码是:
image.png
分析:传入的参数
waitTime:这里传入的默认值是-1
internalLockLeaseTime:成员属性,系统给我们默认设置为30S,Redisson会自动给锁重新添加30s,不用担心业务时间的问题,锁自动过期而导致业务发生问题,点进源码可以查看到
image.png
最终获取到的参数是:毫秒为单位,30秒的过期时间
image.png
TimeUnit.MILLISECONDS:时间单位为毫秒
threadId:当前线程id
RedisCommands.EVAL_LONG:点进源码可以查看
image.png
这里就是具体实现分布式锁(可重入锁的具体实现了)tryLockInnerAsync 方法

    <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));
    }

下面我们就具体来分析下是什么含义:
分析各个含义:
:::success
1、tryLockInnerAsync()方法参数
:::

  • tryLockInnerAsync: 这是一个异步方法,试图获取锁。

  • : 这是一个泛型方法,可以返回任意类型的结果。

  • long waitTime: 等待时间,以指定的时间单位表示。

  • long leaseTime: 锁的租约时间,也就是key的过期时间,也是以指定的时间单位表示。

  • TimeUnit unit: 时间单位,例如毫秒、秒等。

  • long threadId: 线程ID,用于标识请求锁的线程。

  • RedisStrictCommand command: Redis 命令,执行锁操作。
    :::success
    2、evalWriteAsync()方法参数:
    :::
    对应的参数列表:
    image.png

  • getRawName():可能是锁的唯一标识符,例如锁的资源名称。

  • LongCodec.INSTANCE:可能是用于编码和解码Lua脚本参数的编解码器实例。

  • command:传入的Redis命令。

  • Lua脚本:这是Lua脚本的主体,用于执行锁逻辑。

  • Collections.singletonList(getRawName()):传给 evalWriteAsync 的键列表,这里只包含一个键。也就是我们从一开始设置的key的名称lock,通过集合工具类 Collections创建一个单数据集合。image.png

  • unit.toMillis(leaseTime):将key的过期时间转换为毫秒,这里系统默认是30秒。这个用于后续传入脚本的中的值,是params第一个参数,这里的params是动态数组,可以传入多个。

  • getLockName(threadId):据线程ID生成的锁名称。这个用于后续传入脚本的中的值,是params第二个参数,这里的params是动态数组,可以传入多个。
    :::success
    3、evalWriteAsync()方法实现过程:具体的逻辑实现
    :::

  • 通过调用 evalWriteAsync 方法执行 Redis 脚本,该脚本尝试获取锁。

  • 脚本首先检查锁是否存在,如果不存在,则增加锁的计数器并设置过期时间,表示当前线程获得了锁。

  • 如果锁存在且由当前线程持有,则增加计数器并更新过期时间。

  • 如果锁存在但由其他线程持有,则返回锁的剩余过期时间。
    :::success
    4、脚本解析:lua脚本
    :::
    image.png

  • redis.call(‘exists’, KEYS[1]) == 0: 检查键是否存在,如果不存在则说明锁可用。

  • redis.call(‘hincrby’, KEYS[1], ARGV[2], 1): 增加哈希表中指定字段的值,即增加锁的计数器。用的redis中的数据类型是hash。

  • redis.call(‘pexpire’, KEYS[1], ARGV[1]): 设置键的过期时间,以毫秒为单位。

  • return nil;执行完第一个if分支语句,执行成功返回nil,返回空值。

  • redis.call(‘hexists’, KEYS[1], ARGV[2]) == 1: 检查哈希表中是否存在指定字段,即检查当前线程是否已持有锁。满足条件,往下执行。

  • redis.call(‘hincrby’, KEYS[1], ARGV[2], 1):锁计数+1的操作。 增加哈希表中指定字段的值,即增加锁的计数器。

  • redis.call(‘pexpire’, KEYS[1], ARGV[1]): 继续设置锁的过期时间,默认系统设置参数为30秒

  • redis.call(‘pttl’, KEYS[1]): 返回键的剩余过期时间,以毫秒为单位。

KEYS[1] ARGV[1] ARGV[2] 分别传入的值是:Collections.singletonList(getRawName())、unit.toMillis(leaseTime)

  • KEYS[1]: 这是表示第一个键的占位符。这里传入的值为lock ,也就是在Collections.singletonList(getRawName())获取到的值。这里传入的是"lock"字符串。 Redis 的 Lua 脚本中,KEYS 表示所有的键,索引从1开始。在这个方法中,KEYS[1] 应该表示锁的名称,即被用来标识这个锁的唯一键。这个键通常是一个 Redis 的键(key),可以是一个字符串。
  • ARGV[1]: 这是表示第一个参数的占位符。这里传入的是unit.toMillis(leaseTime) 返回的值,默认是30s的过期时间。在 Redis 的 Lua 脚本中,ARGV 表示所有的参数,索引也是从1开始的。在这个方法中,ARGV[1] 表示锁的过期时间,以毫秒为单位。这个参数是通过 leaseTime 经过时间单位转换后传入的值。
  • ARGV[2]: 这是表示第二个参数的占位符。ARGV[2] 应该表示当前线程的标识符,即用来标识请求锁的线程的唯一值。这个参数是通过 threadId 传入的值。

总体来说:这段代码是一个异步的尝试获取锁的方法tryLockInnerAsync()。调用执行lua脚本的方法evalWriteAsync();

总体实现可重入锁的思路为:

  • 第一种情况:首先检查锁是否(key)存在,如果不存在,则增加锁的计数器并设置过期时间为30S,表示当前线程获得了锁。设置锁成功,并且返回null。
  • 第二种情况:再次进来:如果锁存在且由当前线程持有,则增加计数器并更新过期时间。将锁的过期时间重置为30S,实现锁的可重入。设置成功,返回null。
  • 第三种情况:如果锁存在但由其他线程持有,则返回锁的剩余过期时间。
  • 11
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值