锁释放
RedissonFairLock.unlockInnerAsync()方法
这和加锁的逻辑没有太大区别
也就是说在客户端A他释放锁的时候,也会走while true的脚本逻辑,看一下有序集合中的元素的timeout时间如果小于了当前时间,就认为他的那个排队就过期了,就删除他,让他后面重新尝试获取锁的时候重排序
while true的逻辑,比如说客户端B或者客户端C,他们用的是tryAcquire()方法,他们其实设置了一个获取锁超时的时间,比如说他们在队列里排队,但是尝试获取锁超过了20秒,人家就不再尝试获取锁了
此时他们还是在队列和有序集合里占了一个坑位,while true的逻辑就可以保证说剔除掉这种不再尝试获取锁的客户端,有序集合里的timeout分数就不会刷新了,随着时间的推移,肯定就会剔除掉他
如果客户端宕机了,也会导致他就不会重新尝试来获取锁,也就不会刷新有序集合中的timeout分数,不会延长timeout分数,while true的逻辑也可以剔除掉这种宕机的客户端在队列里的占用
因为网络延迟等各种因素在里面,可能会在等待锁时间过长的时候,触发各个客户端的排队的顺序的重排序,有的客户端如果在队列里等待时间过长了,那么其实是可以触发一次队列的重排序的
他在这里发布一个锁被释放的消息,肯定在他的源码中是有一些人是订阅了这个释放锁的消息的,此时他们就可以得到一个锁被释放掉的通知
排队加锁
如果客户端A释放了锁,删除了锁key之后,客户端B和客户端C是如何按照顺序依次加锁的。
要记得,刚才我们经历了队列重拍,排在队头的是客户端C,后面是客户端B
假设锁被释放掉了之后,如果客户端B先来尝试加锁
客户端B加锁失败
10:00:40,锁已经被释放了,客户端B来尝试重新加锁
10:01:04 <= 10:00:40?不成立
exists anyLock = 0,当前锁不存在;exists redisson_lock_queue:{anyLock} = 0,要不然就是队列不存在,但是现在队列是存在的;lindex redisson_lock_queue:{anyLock} 0 = UUID_02:threadId_02,队列存在,但是排在队列头部的不是客户端B的线程
所以上面整体条件不成立,无法加锁
ttl = 10:01:04 - 10:00:40 = 24000毫秒
timeout = 24000 + 10:00:40 + 5000 = 10:01:09
zadd指令,刷新一下客户端B在有序集合中的timeout分数,10:01:09
哪怕是锁释放掉了,其他各个客户端来尝试重新加锁也是不行的,因为此时排在队头的不是这个客户端也不行,此时只会重新计算timeout分数刷新一下有序集合中的timeout分数罢了
客户端C加锁成功
此时客户端C来尝试加锁会如何?
anyLock锁key不存在的;队列是存在的;队列的队头就是客户端C,所以此时加锁的条件成立了,进入加锁的逻辑
lpop redisson_lock_queue:{anyLock},将队列中的第一个元素弹出来
zrem redisson_lock_timeout:{anyLock} UUID_03:threadId_03,将有序集合中的客户端C的线程id的元素给删除掉
hset anyLock UUID_03:threadId_03 1,加锁
pexpire anyLock 30000,设置生存时间为30000毫秒
完成加锁,而且客户端C从队列中出队,此时排在队头的就是客户端B了
获取锁超时,其他客户端获取不到锁,一定会在java代码里进入一个while true死循环,一定时间内没有获取到锁,就返回false标识获取锁失败,过了一段时间,只要没有刷新有序集合中的timeout分数,就会自然被lua脚本里的while true逻辑给清理掉
超时自动释放锁,不会开启lock watchdog后台定时调度的任务