Redisson分布式锁学习总结:写锁 RedissonWriteLock#unLock 释放锁源码分析

1、RedissonWriteLock 之 lua 脚本释放锁

关于 RedissonWriteLock 释放锁的源码,我们这里也是不再做分析了,因为 RedissonWriteLock 也是基于 RedissonLock 做的扩展,所以释放锁的源码也是和 RedissonLock 保持一致的,我们这里只需要分析 lua 脚本是如何执行即可。

分析前,我们定一下加锁的key:

RReadWriteLock readWriteLock = client.getReadWriteLock("myLock");

RedissonWriteLock#unlockInnerAsync:

@Override
protected RFuture<Boolean> unlockInnerAsync(long threadId) {
    return evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
            "local mode = redis.call('hget', KEYS[1], 'mode'); " +
            "if (mode == false) then " +
                "redis.call('publish', KEYS[2], ARGV[1]); " +
                "return 1; " +
            "end;" +
            "if (mode == 'write') then " +
                "local lockExists = redis.call('hexists', KEYS[1], ARGV[3]); " +
                "if (lockExists == 0) then " +
                    "return nil;" +
                "else " +
                    "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('hdel', KEYS[1], ARGV[3]); " +
                        "if (redis.call('hlen', KEYS[1]) == 1) then " +
                            "redis.call('del', KEYS[1]); " +
                            "redis.call('publish', KEYS[2], ARGV[1]); " + 
                        "else " +
                            // has unlocked read-locks
                            "redis.call('hset', KEYS[1], 'mode', 'read'); " +
                        "end; " +
                        "return 1; "+
                    "end; " +
                "end; " +
            "end; "
            + "return nil;",
    Arrays.<Object>asList(getName(), getChannelName()), 
    LockPubSub.READ_UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId));
}

我们可以看到,这读锁释放锁的lua脚本不算长,一步一步分析就可以了。

1.1、KEYS

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

  • getName(): 锁key
  • getChannelName():prefixName(“redisson_rwlock”, getName()) -> redisson_rwlock:{myLock}

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

1.2、ARGVS

LockPubSub.UNLOCK_MESSAGE, getLockName(threadId):

  • LockPubSub.UNLOCK_MESSAGE:0L
  • getLockName(threadId):super.getLockName(threadId) + “:write” -> 客户端ID(UUID):线程ID(threadId):write

ARGVS:[0L,“UUID:threadId:write”]

1.3、lua 脚本分析

1、分支一:锁模式不存在,往锁对应的channel发送消息

场景:

  • 如果锁模式不存在,那么证明没有线程持有读写锁
  • 当前线程即使没有持有锁,但还是调用了释放锁的方法

lua脚本:

"local mode = redis.call('hget', KEYS[1], 'mode'); " +
"if (mode == false) then " +
    "redis.call('publish', KEYS[2], ARGV[1]); " +
    "return 1; " +
"end; "

分析:

  1. 利用 hget 命令获取读写锁的模式

    hget myLock mode
    
  2. 如果锁模式为空,往读写锁对应的channel发送释放锁的消息,然后返回1,lua脚本执行完毕

    publish redisson_rwlock:{myLock} 0
    
2、分支二:锁模式为写锁,但当前线程没有持有锁

场景:

  • 锁存在,但当前线程没有持有锁

lua脚本:

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

分析:

  1. 利用 hexists 命令判断当前线程是否持有锁

    hexists myLock UUID:threadId:write
    
  2. 如果不存在直接返回null,表示释放锁失败

3、分支三:锁模式为写锁,当前线程持有锁,并且持有锁数量大于1

场景:

  • 锁存在,锁模式为写锁
  • 当前线程持有写锁,且持有数量大于1

lua脚本:

"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
"if (counter > 0) then " +
    "redis.call('pexpire', KEYS[1], ARGV[2]); " +
    "return 0; " +
"else "

分析:

  1. 利用 hincrby 命令扣减持有锁数量
    hincrby myLock UUID:threadId:write  -1
    
  2. 扣减持有锁数量后,剩余持有锁数量大于0,利用 pexpire 命令重新刷新锁过期时间
    pexpire myLock 30000
    
4、分支四:锁模式为写锁,当前线程持有锁数量为1

场景:

  • 锁存在,锁模式为写锁
  • 当前线程持有写锁,且持有数量等于1
  • 还可能存在一种情况:当前线程不但持有写锁,还持有读锁;如果持有读锁,那么在释放写锁后,需要设置锁模式为读锁

lua脚本:

"redis.call('hdel', KEYS[1], ARGV[3]); " +
"if (redis.call('hlen', KEYS[1]) == 1) then " +
    "redis.call('del', KEYS[1]); " +
    "redis.call('publish', KEYS[2], ARGV[1]); " + 
"else " +
    // has unlocked read-locks
    "redis.call('hset', KEYS[1], 'mode', 'read'); " +
"end; " +
"return 1; "

分析:

  1. 利用 del 命令删除写锁记录

    hdel myLock UUID:threadId:write
    
  2. 删除写锁记录后,利用 hlen 判断锁map里还存在多少个key

    hlen myLock
    
    • 如果 key 数量等于1,证明当前线程不再持有任何锁,利用 del 命令删除锁map,利用 publish 命令发布释放锁消息
      del myLock
      
      public redisson_rwlock:{myLock} 0
      
    • 如果 key 数量大于1,证明当前线程还持有读锁,利用 hset 命令将锁模式设置为读锁
      hset myLock mode read
      
  3. 最后返回1,释放锁成功,终止lua脚本执行

5、最后:如果上面的分支都不满足,证明当前线程并没有持有写锁,直接返回null即可。

2、最后

到此,关于 Redisson 读写锁的原理分析都基本分析完了,包括读锁、写锁的加锁和释放锁原理。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值