文章目录
- 1.关于锁的重试机制:
- 2.锁的超时问题
1.关于锁的重试机制:
进一步进入tryLock函数内部
2.锁的超时问题
加入目前获取锁成功了,我有一个剩余的有效期,万一业务阻塞了,TTL到期了,其他线程又进来拿锁,导致线程安全问题。我必须保证我的锁是执行完业务释放的,而不是业务阻塞导致锁过期释放的,这个问题如何解决呢?
答案是有一种不断刷新任务有效期的看门狗机制,没过10秒钟自动刷新任务有效期,从而避免了业务因为线程阻塞导致锁过期自动释放,导致其他线程趁虚而入导致的线程安全问题(先说一嘴答案,免得后面看蒙蔽了,绕晕了,因为实在是很复杂!)
我们可以继续深入源码了解,首先进入上图中的tryAcquire函数中,
发现调用了tryAcquireAsync函数
又进入到tryAcquireAsync函数内部
进一步解释scheduleExpirationRenewal的作用
进一步解释EXPIRATION_RENEWAL_MAP的作用,这里的MAP是存储了entryName—>K,entry----V,这里entryName是连接名和锁名组合成的(为啥?可以看下一幅图),不同的业务组成了不同的锁名称,因此可以相当于不同业务的锁是独立的,在MAP中互不干扰。
解释一下EXPIRATION_RENEWAL_MAP.putIfAbsent
的作用,就是如果MAP里面存在了这个key,我就不存他这个key了,并返回上一次这个key对应的Value,不会参数后来的val覆盖先前的val的情况,如果不存在才直接插入kv,如果这个MAP写的是put
的话,在key重复插入的情况下,会导致后来的覆盖前面的val的情况。这在可重入锁中是不能发生的,因此putIfAbsent
实现了帮助实现了可重入锁
的功能,不管后来的锁重入几次,保证获得的都是之前的entry。并且如果这个key是新插入的话,会额外执行一个更新有效期的操作this.renewExpiration();
而this.renewExpiration();
在下两张里面里面解释
this.renewExpiration();
解释
renewExpiration
函数内部的RFuture<Boolean> future = RedissonLock.this.renewExpirationAsync(threadId);
又是一个关键的函数,又跳入renewExpirationAsync(threadId)
内部一探究竟,又是一个异步刷新有效期的函数
由于这段代码在class里面写的实在是太难看了,直接copy通义灵码的分析结果,说白了,就是重置当前threadId线程持有的锁的有效期,但是你注意,这里更新完有效期之后又递归调用自己,10秒钟之后又刷新有效期。
最后这个任务的执行流程大概是这样的,等待了10秒之后执行刷新自己的任务有效期,还剩下30秒,然后过了10s之后,又递归调用自己,还剩余20s的时候再次刷新任务有效期,重复循环往返!
理解到这层意图,我们又跳回到前面的
那问题来了,这个任务什么时候才会释放呢?什么时候才会取消呢?
这当然是在释放锁的时候,这个任务才会被释放!
我们跳回刚开始的@Test方法里面看代码
进入这个指定的包里面的函数
最后总结第二部分的问题?如何解决的业务因为线程阻塞导致业务还没执行完就锁过期自动释放,导致其他线程趁虚而入导致的线程安全问题?通过这一段上面的源码剖析,我们知道是有一种不断刷新任务有效期的看门狗机制,没过10秒钟自动刷新任务有效期,从而避免了业务因为线程阻塞导致锁过期自动释放,导致其他线程趁虚而入导致的线程安全问题。
我们在最后总结分布式锁的流程图加深印象和理解: