Redisson分布式锁学习总结:可重入锁 RedissonLock#unlock 释放锁源码分析

1、unlock() 释放锁源码

可重入锁 RedissonLock 释放锁的源码还是比较简单的,我们可以分为两步:第一步是执行释放锁的lua脚本,第二步就是停止 watchdog 的运行。

RedissonLock#unlockInnerAsync:

@Override
public RFuture<Void> unlockAsync(long threadId) {
    RPromise<Void> result = new RedissonPromise<Void>();
    RFuture<Boolean> future = unlockInnerAsync(threadId);

    future.onComplete((opStatus, e) -> {
        // 停止 watchdog 的定时续过期时间,其实就是将对应的 ExpirationEntry 从 EXPIRATION_RENEWAL_MAP 中移除,当 watchdog 执行时发现当前客户端当前线程没有 ExpirationEntry 了,那么就会停止执行了。
        cancelExpirationRenewal(threadId);

        if (e != null) {
            result.tryFailure(e);
            return;
        }
        
        // 如果 lua脚本返回的是null,证明当前线程之前并没有成功获取锁,执行tryFailure方法
        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;
        }
        // 成功释放锁,执行 trySuccess 方法
        result.trySuccess(null);
    });

    return result;
}

2、释放锁 lua 脚本

上面我们看到,释放锁的源码还是比较少的,我们也看到大概就分为几个步骤,但其实最重要的释放锁逻辑在于 lua 脚本,所以下面我们将一个步骤一个步骤去分析。

protected RFuture<Boolean> unlockInnerAsync(long threadId) {
    return evalWriteAsync(getName(), 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(getName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId));
}

2.1、KEYS

Arrays.asList(getName(), getChannelName())

KEYS:[“myLock”,“redisson_lock__channel:{myLock}”]

2.2、ARGVS

LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId)

ARGVS:[0,30000,“UUID:threadId”]

2.3、lua 脚本分析

1、分支一:当前线程没有加过锁

lua 脚本:

"if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
    "return nil;" +
"end; " +

分析:

  1. 利用 hexists 判断是否为当线程加锁
    hexists myLock UUID:threadId
    
  2. 加锁记录不存在,则直接返回nil,即null,表示释放锁失败,终止lua脚本执行
2、当前线程加过锁,扣减加锁次数

lua脚本:

local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1);

分析:

  1. 利用 hincrby 命令扣减加锁次数
    hincrby myLock UUID:threadId -1
    
3、分支二:当前线程加锁次数大于1

场景:

  • 当前线程持有锁数量大于1
  • 当释放一次锁后,需刷新锁的过期时间,不用删除锁

lua 脚本:

"if (counter > 0) then " +
    "redis.call('pexpire', KEYS[1], ARGV[2]); " +
"return 0; " +

分析:

  1. 剩余持有锁次数大于0,利用 pexpire 命令刷新锁过期时间

    pexpire myLock 30000
    
  2. 返回0,表示释放锁成功,终止lua脚本的执行

4、分支三:当前线程仅持有一次锁

场景:

  • 当前线程仅持有一次锁
  • 当释放锁后,需删除对应锁 key,并往channel发送释放锁消息,通知其他线程可以获取锁了

lua脚本:

"else " +
    "redis.call('del', KEYS[1]); " +
    "redis.call('publish', KEYS[2], ARGV[1]); " +
"return 1; " +
"end; " +

分析:

  1. 利用 del 删除锁key

    del myLock
    
  2. 往锁对应channel发送释放锁消息

    publish redisson_lock__channel:{myLock} 0
    

    其实就是我们上篇说的,如果获取锁失败,再等待的时候,同时会订阅一个channel,当接收到channel有消息,那么证明有客户端释放锁了, 就可以从等待状态中醒过来,重新尝试获取锁。这样可以避免不必要的等待。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值