假设现在已经有各种锁的重入什么的,那如何释放锁?
读锁+读锁
假如说,同一个线程多次加读锁,或者不同的线程加了多个读锁
当前的锁结构长这样
anyLock: {
“mode”: “read”,
“UUID_01:threadId_01”: 2,
“UUID_02:threadId_02”: 1
}
{anyLock}:UUID_01:threadId_01:rwlock_timeout:1 1
{anyLock}:UUID_01:threadId_01:rwlock_timeout:2 1
{anyLock}:UUID_02:threadId_02:rwlock_timeout:1 1
写锁+读锁
或者是同一个线程先加了写锁,后加了读锁,我们看是如果单独释放读锁的
anyLock: {
“mode”: “write”,
“UUID_01:threadId_01:write”: 1,
“UUID_01:threadId_01”: 1
}
{anyLock}:UUID_01:threadId_01:rwlock_timeout:1 1
针对这些情况,如何释放读锁,代码如下
RedissonReadLock.unlockInnerAsync();
我们来分析一下,假如说客户端A线程1来释放读锁
先看参数
KEYS[1] = anyLock
KEYS[2] = redisson_rwlock:{anyLock}
KEYS[3] = {anyLock}:UUID_01:threadId_01:rwlock_timeout
KEYS[4] = {anyLock}
ARGV[1] = 0
ARGV[2] = UUID_01:threadId_01
前两行
local mode = redis.call(‘hget’, KEYS[1], ‘mode’);
if (mode == false) then
判断这个key存在不存在,现在已经加了锁,肯定是存在的
再往下
local lockExists = redis.call(‘hexists’, KEYS[1], ARGV[2]);
判断anyLock这个hash表中UUID_01:threadId_01 这个key是否存在,存在的话返回对应的值
现在也肯定是存在的,所以会返回出来一个值
hincrby增量更新,但是后面是-1,所以是原本的值 -1,并且返回更新后的值
local counter = redis.call(‘hincrby’, KEYS[1], ARGV[2], -1);
这两行命令要注意一下,什么情况下值会不等于0?
如果同一个线程多次加了读锁的情况下,那要释放多次,这里只-1,所以肯定是大于0 的,
如果=0的话,就要删除anyLock这个hash表中UUID_01:threadId_01这个key
这里就是读锁+读锁可重入的释放
if (counter == 0) then
redis.call(‘hdel’, KEYS[1], ARGV[2]);
这一行,要删除一个普通的键值对,
redis.call(‘del’, KEYS[3] … ‘:’ … (counter+1));
KEYS[3] = {anyLock}:UUID_01:threadId_01:rwlock_timeout
后面为什么是 (counter+1) 因为刚才不是-1了吗,这里要删除的是原本的值
那拼接后就是{anyLock}:UUID_01:threadId_01:rwlock_timeout:1
这里要删除这个普通键值对,看最上面我们假设的数据结构,确实有这个
如果删除的是重入后的第二次释放
那拼接后就是{anyLock}:UUID_01:threadId_01:rwlock_timeout:2
也是符合重入几次就释放几次的逻辑。
继续往下,这个是判断anyLock这个hash表中的键值对数量,还大于1个吗
if (redis.call(‘hlen’, KEYS[1]) > 1) then
默认是有一个mode的key,每有一个线程加锁,就会新建一个UUID_XX:threadId_XX这种key,那如果有多个读锁的话,现在只释放了一个,key的数量肯定是大于1的
进入if判断里面
定义一个-3的值,后面再说具体什么作用
local maxRemainTime = -3;
取出来anyLock这个hash表中的所有key
local keys = redis.call(‘hkeys’, KEYS[1]);
进入循环for n, key in ipairs(keys) do ,循环取出所有的key
根据hash表中每个key取出来所有的值
counter = tonumber(redis.call(‘hget’, KEYS[1], key));
判断这些值是否是数字,如果是数字的话进入下面的逻辑
if type(counter) == ‘number’ then
如果这个值是多个的话,要从最大开始循环
for i=counter, 1, -1 do
这里是获取过期以毫秒为单位的时间的
local remainTime = redis.call(‘pttl’, KEYS[4] … ‘:’ … key … ‘:rwlock_timeout:’ … i);
maxRemainTime = math.max(remainTime, maxRemainTime);
KEYS[4]={anyLock}
key = UUID_01:threadId_01 /UUID_02:threadId_02 就是所有的key
那最终拼出来的就是
{anyLock}:UUID_01:threadId_01::rwlock_timeout:2
{anyLock}:UUID_02:threadId_02::rwlock_timeout:1
那这里是通过hash表中还存在的读锁,遍历对应的普通键值对锁,来获取他们的最大过期时间
if maxRemainTime > 0 then
redis.call(‘pexpire’, KEYS[1], maxRemainTime);
这个判断是为了延长anyLock这个hash表的过期时间,和对应普通键值锁的过期时间,防止提前过期
if mode == ‘write’ then
return 0
这个是为了应对,先加了写锁,再加读锁的情况。现在读锁已经释放了,写锁不能在这里处理。所以这个逻辑就是判断,如果是写锁的话,要直接返回。
redis.call(‘del’, KEYS[1]);
redis.call(‘publish’, KEYS[2], ARGV[1]);
如果读锁还有其他线程,或者是重入的锁还没释放完,不会走到这里的。都在前面的判断已经拦截了
也就是说,只要能走到这里的都是读锁中所有的线程都释放完毕了,那就要删除这个anyLock的hash锁了。 并且将这个消息进行广播。