Appearance
一、读写锁(ReadWriteLock)介绍
基于Redis的Redisson分布式可重入读写锁RReadWriteLock Java对象实现了java.util.concurrent.locks.ReadWriteLock接口。其中读锁和写锁都继承了RLock接口。分布式可重入读写锁允许同时有多个读锁和一个写锁处于加锁状态
java
RReadWriteLock rwlock = redisson.getReadWriteLock("anyRWLock");
// 最常见的使用方法
rwlock.readLock().lock();
// 或
rwlock.writeLock().lock();
当多个客户端同时加读锁,是不会互斥的,多个客户端可以同时加这个读锁,读锁和读锁是不互斥的。如果有人加了读锁,此时就不能加写锁,任何人都不能加写锁了,读锁和写锁是互斥的,如果有人加了写锁,此时任何人都不能加写锁了,写锁和写锁也是互斥的
二、源码分析
RedissonReadLock是RedissonLock的子类,那么就进去到这个子类里去看看做了些什么事情
java
public class RedissonReadWriteLock extends RedissonExpirable implements RReadWriteLock {
public RedissonReadWriteLock(CommandAsyncExecutor commandExecutor, String name) {
super(commandExecutor, name);
}
public RLock readLock() {
return new RedissonReadLock(this.commandExecutor, this.getName());
}
public RLock writeLock() {
return new RedissonWriteLock(this.commandExecutor, this.getName());
}
}
可以看到读锁是RedissonReadLock、写锁是RedissonWriteLock
三、readLock源码分析
我们先来看看读锁,RedissonReadLock ,其实我们大概现在都知道了,加锁源码基本和之前的可重入锁加锁无区别,唯一的差异就是在 Lua 脚本我们要研究 加锁 释放锁,所以下面着重分析 Lua 脚本,tryLockInnerAsync充血加锁逻辑
java
@Override
<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
internalLockLeaseTime = unit.toMillis(leaseTime);
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
"local mode = redis.call('hget', KEYS[1], 'mode'); " +
"if (mode == false) then " +
"redis.call('hset', KEYS[1], 'mode', 'read'); " +
"redis.call('hset', KEYS[1], ARGV[2], 1); " +
"redis.call('set', KEYS[2] .. ':1', 1); " +
"redis.call('pexpire', KEYS[2] .. ':1', ARGV[1]); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
"if (mode == 'read') or (mode == 'write' and redis.call('hexists', KEYS[1], ARGV[3]) == 1) then " +
"local ind = redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"local key = KEYS[2] .. ':' .. ind;" +
"redis.call('set', key, 1); " +
"redis.call('pexpire', key, ARGV[1]); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end;" +
"return redis.call('pttl', KEYS[1]);",
Arrays.<Object>asList(getName(), getReadWriteTimeoutNamePrefix(threadId)),
internalLockLeaseTime, getLockName(threadId), getWriteLockName(threadId));
}
参数说明
- KEYS[1]:锁名字 anyRWLock
- KEYS[2]:锁超时 key {锁名字}:UUID:ThreadId:rwlock_timeout 组成的字符串,{anyRWLock}:e70b1307-9ddd-43de-ac9d-9c42b5c99a0d:1:rwlock_timeout
- ARGV[1]:锁时间,默认 30s
- ARGV[2]:当前线程,UUID:ThreadId 组成的字符串,e70b1307-9ddd-43de-ac9d-9c42b5c99a0d:1
- ARGV[3]:写锁名字,getWriteLockName(threadId) 写锁名字,UUID:ThreadId:write 组成的字符串, e70b1307-9ddd-43de-ac9d-9c42b5c99a0d:1:write
首次加锁处理分析
java
"local mode = redis.call('hget', KEYS[1], 'mode'); " +
"if (mode == false) then " +
"redis.call('hset', KEYS[1], 'mode', 'read'); " +
"redis.call('hset', KEYS[1], ARGV[2], 1); " +
"redis.call('set', KEYS[2] .. ':1', 1); " +
"redis.call('pexpire', KEYS[2] .. ':1', ARGV[1]); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
通过hget获取锁,当锁不存在,继续执行下面的逻辑:
- hset设置锁 anyRWLock 的 hash里kv值,key = mode ,value = read,表示这是个读锁
- hset设置锁 anyRWLock 的 hash里kv值,key = e70b1307-9ddd-43de-ac9d-9c42b5c99a0d:1,value= 1
- set设置锁,key字符串: {anyRWLock}:e70b1307-9ddd-43de-ac9d-9c42b5c99a0d:1:rwlock_timeout:1 ,value = 1
- pexpire设置两个 RedisKey 的过期时间
读锁重入处理分析
java
"if (mode == 'read') or (mode == 'write' and redis.call('hexists', KEYS[1], ARGV[3]) == 1) then " +
"local ind = redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"local key = KEYS[2] .. ':' .. ind;" +
"redis.call('set', key, 1); " +
"redis.call('pexpire', key, ARGV[1]); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end;" +
通过hget获取锁,当锁存在,且是读锁,说明已经加过读锁,则进行读锁重入,执行下面的逻辑:
- 对锁 anyRWLock 的 e70b1307-9ddd-43de-ac9d-9c42b5c99a0d:1(当前线程)值自增 1 表是重入
- 再创建 {anyRWLock}:e70b1307-9ddd-43de-ac9d-9c42b5c99a0d:1:rwlock_timeout:2的key,ind 为hincrby结果,hincrby返回是2
- set 字符串key{anyRWLock}:e70b1307-9ddd-43de-ac9d-9c42b5c99a0d:1:rwlock_timeout:2 值为1
- expire设置两个 RedisKey 的过期时间
Reids存储数据结构为:
java
anyLock: {
"mode": "read",
"e70b1307-9ddd-43de-ac9d-9c42b5c99a0d:1": 2
}
{anyLock}:UUID_01:threadId_01:rwlock_timeout:1: 1
{anyLock}:UUID_01:threadId_01:rwlock_timeout:2: 1
写读互斥处理分析
已经加了读锁了,此时写锁进来,不满足第一部分,也不满足第二部分,所以直接返回当前锁的剩余时间。然后再 Java 代码中进行 while (true) 自旋等待
java
"if (mode == 'read') or (mode == 'write' and redis.call('hexists', KEYS[1], ARGV[3]) == 1) then " +
"local ind = redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"local key = KEYS[2] .. ':' .. ind;" +
"redis.call('set', key, 1); " +
"redis.call('pexpire', key, ARGV[1]); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end;" +
"return redis.call('pttl', KEYS[1]);",
通过上面可以看出,在读锁的时候:
- 锁 anyRWLock 是哈希表结构的
- 加锁时,会对哈希表设置 mode 字段来表示这个锁是读锁还是写锁,mode = read 表示读锁
- 加锁时,会对哈希表设置当前线程 anyRWLock 的 UUID:ThreadId 字段,值表示重入次数
- 每次加锁,会额外维护一个 key 表示这次锁的超时时间,这个 key 的结构是 {锁名字}:UUID:ThreadId:rwlock_timeout:重入次数
同时不要忘了,我们还有一个watchdog看门狗,会每隔10秒钟去执行一段lua脚本,判断一下当前这个线程是否还持有着这个锁,如果还持有锁,更新一下锁key的生存时间为30000毫秒,保持redis的锁key和java代码中持有的锁是保持同步的
四、writeLock源码分析
java
@Override
<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
internalLockLeaseTime = unit.toMillis(leaseTime);
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
"local mode = redis.call('hget', KEYS[1], 'mode'); " +
"if (mode == false) then " +
"redis.call('hset', KEYS[1], 'mode', 'write'); " +
"redis.call('hset', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
"if (mode == 'write') then " +
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"local currentExpire = redis.call('pttl', KEYS[1]); " +
"redis.call('pexpire', KEYS[1], currentExpire + ARGV[1]); " +
"return nil; " +
"end; " +
"end;" +
"return redis.call('pttl', KEYS[1]);",
Arrays.<Object>asList(getName()),
internalLockLeaseTime, getLockName(threadId));
}
参数说明
- KEYS[1]:当前锁 anyRWLock
- ARGV[1]:锁时间,默认 30s
- ARGV[2]:写锁名字,UUID:ThreadId:write 组成的字符串,c69a9ed4-5c30-4952-814e-c0b94ad03a7f:1:write
首次加锁处理分析
java
"local mode = redis.call('hget', KEYS[1], 'mode'); " +
"if (mode == false) then " +
"redis.call('hset', KEYS[1], 'mode', 'write'); " +
"redis.call('hset', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
通过hget获取锁,当锁不存在,继续执行下面的逻辑:
- hset设置锁 anyRWLock 的 hash里kv值,key = mode ,value = write,表示这是个读锁
- hset设置锁 anyRWLock 的 hash里kv值,key = e70b1307-9ddd-43de-ac9d-9c42b5c99a0d:1,value= 1
- pexpire设置两个 RedisKey 的过期时间
Reids存储数据结构为:
java
anyLock: {
"mode": "write",
"e70b1307-9ddd-43de-ac9d-9c42b5c99a0d:1": 1
}
再次请求加写锁处理分析
java
"if (mode == 'write') then " +
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"local currentExpire = redis.call('pttl', KEYS[1]); " +
"redis.call('pexpire', KEYS[1], currentExpire + ARGV[1]); " +
"return nil; " +
"end; " +
"end;" +
此时再次来加写锁,直接到另一个if语句中,如果当前是同一个线程则进入重入逻辑
- hincrby anyLock UUID_01:threadId_01:write 1
- pexpire anyLock pttl + 30000
通过上面可以看出,在写锁的时候:
- 锁 anyRWLock 是哈希表结构
- 加锁时,会对哈希表设置 mode 字段来表示这个锁是读锁还是写锁,mode = write 表示写锁
- 在 anyRWLock 中再额外维护一个字段 UUID:ThreadId:write 表示重入次数
五、读锁与读锁非互斥
读锁与读锁是非互斥。比如说客户端A,客户端B,两个客户端部署在不同的机器上,都要对anyLock这个锁加读锁,此时是不会互斥的,他们的加锁都可以成功,我们可以根据读锁的lua脚本进行分析
假设,客户端A(UUID_01:threadId_01),先加了一个读锁
java
hset anyLock mode read
hset anyLock UUID_01:threadId_01 1
set {anyLock}:UUID_01:threadId_01:rwlock_timeout:1 1
pexpire {anyLock}:UUID_01:threadId_01:rwlock_timeout:1 30000
pexpire anyLock 30000
此时的Redis里的数据结构则:
java
anyLock: {
"mode": "read",
"UUID_01:threadId_01": 1
}
{anyLock}:UUID_01:threadId_01:rwlock_timeout:1 1
然后此时客户端B在另外一台机器上的,同时也要来加读锁(UUID_02:threadId_02),也要加读锁
java
KEYS[1] = anyLock
KEYS[2] = {anyLock}:UUID_02:threadId_02:rwlock_timeout
ARGV[1] = 30000毫秒
ARGV[2] = UUID_02:threadId_02
ARGV[3] = UUID_02:threadId_02:write
hget anyLock mode,获取到mode = read,此时已经有人加了一把读锁,所以走的是第二部分的lua脚本
java
"if (mode == 'read') or (mode == 'write' and redis.call('hexists', KEYS[1], ARGV[3]) == 1) then " +
"local ind = redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"local key = KEYS[2] .. ':' .. ind;" +
"redis.call('set', key, 1); " +
"redis.call('pexpire', key, ARGV[1]); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end;" +
//代入参数执行lua脚本
//hincrby anyLock UUID_02:threadId_02 1
//set {anyLock}:UUID_02:threadId_02:rwlock_timeout:1 1
//pexpire anyLock 30000
//pexpire {anyLock}:UUID_02:threadId_02:rwlock_timeout:1 30000
ind其实就是累加了1之后的一个值,ind = 1,{anyLock}:UUID_02:threadId_02:rwlock_timeout:1。此时的Redis里的数据结构则:
java
anyLock: {
“mode”: “read”,
“UUID_01:threadId_01”: 1,
“UUID_02:threadId_02”: 1
}
{anyLock}:UUID_01:threadId_01:rwlock_timeout:1 1
{anyLock}:UUID_02:threadId_02:rwlock_timeout:1 1
小结
多个客户端,同时加读锁,读锁与读锁是不互斥的,只会让你不断的在hash里加入哪个客户端也加了一个读锁。每个客户端都会维持一个watchdog,不断的刷新anyLock的生存时间,同时也会刷新那个客户端自己对应的timeout key的生存时间
六、先读后写如何互斥
按照我们上面的客户端A(UUID_01:threadId_01)和客户端B(UUID_02:threadId_02),安排客户端C(UUID_03:threadId_03)来加写锁 执行lua脚本如下:
java
"local mode = redis.call('hget', KEYS[1], 'mode'); " +
"if (mode == false) then " +
"redis.call('hset', KEYS[1], 'mode', 'write'); " +
"redis.call('hset', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
"if (mode == 'write') then " +
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"local currentExpire = redis.call('pttl', KEYS[1]); " +
"redis.call('pexpire', KEYS[1], currentExpire + ARGV[1]); " +
"return nil; " +
"end; " +
"end;" +
"return redis.call('pttl', KEYS[1]);",
hget anyLock mode,mode = read,已经有人加了读锁,不是写锁,此时会直接执行:pttl anyLock,返回一个anyLock的剩余生存时间。会导致客户端C加锁失败,就会不断的尝试重试去加锁,陷入一个死循环。除非是你原先加读锁的人释放了读锁,他这个写锁才能够重构加上去
七、如果先有写锁再有人加读锁是如何互斥的?
假设客户端A先加了一个写锁,按照我们执行的lua脚本逻辑,在redis中的数据格式就如下:
java
anyLock: {
"mode": "write",
"UUID_01:threadId_01:write": 1
}
假设此时客户端B来加读锁,按照lua脚本的参数如下:
java
"if (mode == 'read') or (mode == 'write' and redis.call('hexists', KEYS[1], ARGV[3]) == 1) then " +
"local ind = redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"local key = KEYS[2] .. ':' .. ind;" +
"redis.call('set', key, 1); " +
"redis.call('pexpire', key, ARGV[1]); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end;" +
"return redis.call('pttl', KEYS[1]);",
//KEYS[1] = anyLock
//KEYS[2] = {anyLock}:UUID_02:threadId_02:rwlock_timeout
//ARGV[1] = 30000毫秒
//ARGV[2] = UUID_02:threadId_02
//ARGV[3] = UUID_02:threadId_02:write
hget anyLock mode,mode = write,发现经有人加了一个写锁了
hexists anyLock UUID_02:threadId_02:write,存在的话,如果客户端B自己之前加过写锁的话,此时才能进入这个分支,但是不幸的是,加写锁的是客户端A,所以这里的条件会全部失败,不会成立
返回pttl anyLock,导致加锁失败,不断的陷入死循环不断的重试。导致写锁和读锁,无论如何都会是失败的,互斥的
八、写锁和写锁互斥
还是分两个客户端A和客户端B,客户端A先加了一个写锁,执行的Redis格式如下:
java
KEYS[1] = anyLock
ARGV[1] = 30000
ARGV[2] = UUID_01:threadId_01:write
anyLock: {
"mode": "write",
"UUID_01:threadId_01:write": 1
}
此时客户端B也来尝试加写锁,执行lua脚本如下:
java
"if (mode == 'write') then " +
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"local currentExpire = redis.call('pttl', KEYS[1]); " +
"redis.call('pexpire', KEYS[1], currentExpire + ARGV[1]); " +
"return nil; " +
"end; " +
"end;" +
//KEYS[1] = anyLock
//ARGV[1] = 30000
//ARGV[2] = UUID_02:threadId_02:writ
hget anyLock mode,mode = write,已经有人加了一把写锁 hexists anyLock UUID_02:threadId_02:write,如果之前是客户端B自己加的写锁的话,才能进入下面的分支,但是实际上写锁并不是他加的,是客户端A加的,所以这个分支是不会进入的
直接返回一个ttl时间,导致加锁失败。不同的客户端之间加写锁,是会互斥的。不同的客户端/线程之间,读锁与读锁不互斥,读锁与写锁互斥,写锁与写锁互斥
九、多种可重入加锁分析
分析多种情况:若同一个客户端同一个线程,先加读锁,再加一次读锁;先加读锁,再加一次写锁;先加写锁,再加一次读锁;先加写锁,再加一次写锁,会分别产生什么结果
读锁+读锁
我们先客户端A执行一次读锁,看到lua脚本、参数、redis执行结果
java
"local mode = redis.call('hget', KEYS[1], 'mode'); " +
"if (mode == false) then " +
"redis.call('hset', KEYS[1], 'mode', 'read'); " +
"redis.call('hset', KEYS[1], ARGV[2], 1); " +
"redis.call('set', KEYS[2] .. ':1', 1); " +
"redis.call('pexpire', KEYS[2] .. ':1', ARGV[1]); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
KEYS[1] = anyLock
KEYS[2] = {anyLock}:UUID_01:threadId_01:rwlock_timeout
ARGV[1] = 30000毫秒
ARGV[2] = UUID_01:threadId_01
ARGV[3] = UUID_01:threadId_01:write
anyLock: {
"mode": "read",
"UUID_01:threadId_01": 1
}
{anyLock}:UUID_01:threadId_01:rwlock_timeout:1 1
此时再来一次读锁
java
hget anyLock mode,mode = read //此时已经加过读锁
hincrby anyLock UUID_01:threadId_01 1
set {anyLock}:UUID_01:threadId_01:rwlock_timeout:2 1
pexpire anyLock 30000
pexpire {anyLock}:UUID_01:threadId_01:rwlock_timeout:2 30000
anyLock: {
"mode": "read",
"UUID_01:threadId_01": 2
}
{anyLock}:UUID_01:threadId_01:rwlock_timeout:1 1
{anyLock}:UUID_01:threadId_01:rwlock_timeout:2 1
读锁+写锁
我们先客户端A执行一次读锁,看到lua脚本、参数、redis执行结果
java
"local mode = redis.call('hget', KEYS[1], 'mode'); " +
"if (mode == false) then " +
"redis.call('hset', KEYS[1], 'mode', 'read'); " +
"redis.call('hset', KEYS[1], ARGV[2], 1); " +
"redis.call('set', KEYS[2] .. ':1', 1); " +
"redis.call('pexpire', KEYS[2] .. ':1', ARGV[1]); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
KEYS[1] = anyLock
KEYS[2] = {anyLock}:UUID_01:threadId_01:rwlock_timeout
ARGV[1] = 30000毫秒
ARGV[2] = UUID_01:threadId_01
ARGV[3] = UUID_01:threadId_01:write
anyLock: {
"mode": "read",
"UUID_01:threadId_01": 1
}
{anyLock}:UUID_01:threadId_01:rwlock_timeout:1 1
此时再来一次写锁
java
"local mode = redis.call('hget', KEYS[1], 'mode'); " +
"if (mode == false) then " +
"redis.call('hset', KEYS[1], 'mode', 'write'); " +
"redis.call('hset', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
"if (mode == 'write') then " +
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"local currentExpire = redis.call('pttl', KEYS[1]); " +
"redis.call('pexpire', KEYS[1], currentExpire + ARGV[1]); " +
"return nil; " +
"end; " +
"end;" +
"return redis.call('pttl', KEYS[1]);",
我们此时hget anyLock mode,mode = read,不符合条件,此时同一个客户端同一个线程,先读锁再写锁,是互斥的,会导致加锁失败
写锁+读锁
我们先客户端A执行一次写锁,看到lua脚本、参数、redis执行结果
java
"local mode = redis.call('hget', KEYS[1], 'mode'); " +
"if (mode == false) then " +
"redis.call('hset', KEYS[1], 'mode', 'write'); " +
"redis.call('hset', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
KEYS[1] = anyLock
ARGV[1] = 30000
ARGV[2] = UUID_01:threadId_01:write
anyLock: {
"mode": "write",
"UUID_01:threadId_01:write": 1
}
再来加一次读锁
java
"if (mode == 'read') or (mode == 'write' and redis.call('hexists', KEYS[1], ARGV[3]) == 1) then " +
"local ind = redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"local key = KEYS[2] .. ':' .. ind;" +
"redis.call('set', key, 1); " +
"redis.call('pexpire', key, ARGV[1]); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end;" +
"return redis.call('pttl', KEYS[1]);",
hincrby anyLock UUID_01:threadId_01 1 //也就是说此时,加了一个读锁
set {anyLock}:UUID_01:threadId_01:rwlock_timeout:1 1,
pexpire anyLock 30000
pexpire {anyLock}:UUID_01:threadId_01:rwlock_timeout:1 30000
anyLock: {
"mode": "write",
"UUID_01:threadId_01:write": 1,
"UUID_01:threadId_01": 1
}
{anyLock}:UUID_01:threadId_01:rwlock_timeout:1
hexists anyLock UUID_01:threadId_01:write,判断一下之前是否是自己加过一次写锁,此时是成立的,也就是之前加过一次写锁的,同一个客户端同一个线程,然后加读锁,是可以加的,默认是在同一个线程写锁的期间,可以多次加读锁
写锁+写锁
客户端A先加一次写锁,看到lua脚本、参数、redis执行结果
java
"local mode = redis.call('hget', KEYS[1], 'mode'); " +
"if (mode == false) then " +
"redis.call('hset', KEYS[1], 'mode', 'write'); " +
"redis.call('hset', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
KEYS[1] = anyLock
ARGV[1] = 30000
ARGV[2] = UUID_01:threadId_01:write
anyLock: {
"mode": "write",
"UUID_01:threadId_01:write": 1
}
再来加一次写锁
java
"if (mode == 'write') then " +
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"local currentExpire = redis.call('pttl', KEYS[1]); " +
"redis.call('pexpire', KEYS[1], currentExpire + ARGV[1]); " +
"return nil; " +
"end; " +
"end;" +
hexists anyLock UUID_01:threadId_01:write,是否存在?之前是他自己加的写锁,所以满足==1进入IF操作
java
hincrby anyLock UUID_01:threadId_01:write 1
pexpire anyLock 50000
anyLock: {
"mode": "write",
"UUID_01:threadId_01:write": 2
}
所以通过源码可以看到,同一个客户端同一个线程,多次加写锁,是可以重入加锁的读写锁其实也是一种可重入锁