Redis分布式锁实现Redisson 15问(三)
六、如何主动释放锁和避免其它线程释放了自己加的锁
当业务执行完成之后,肯定需要主动释放锁,那么为什么需要主动释放锁呢?
第一,假设你任务执行完,没有手动释放锁,如果没有指定锁的超时时间,那么因为有看门狗机制,势必会导致这个锁无法释放,那么就可能造成死锁的问题。
第二,如果你指定了锁超时时间(锁超时自动释放逻辑后面会说),虽然并不会造成死锁的问题,但是会造成资源浪费的问题。假设你设置的过期时间是30s,但是你的任务2s就完成了,那么这个锁还会白白被占有28s的时间,这28s内其它线程都无法成功加锁。
所以任务完成之后,一定需要主动释放锁。
那么Redisson是如何主动释放锁和避免其它线程释放了自己加的锁?
主动释放锁是通过unlock方法来完成的,接下来就分析一下unlock方法的实现。unlock会调用unlockAsync,传入当然释放线程的id,代表了当前线程来释放锁,unlock其实也是将unlockAsync的异步操作转为同步操作。
unlockAsync最后会调用RedissonLock的unlockInnerAsync来实现释放锁的逻辑。
也是执行一段lua脚本。
1)先判断来释放锁的线程是不是加锁的线程,如果不是,那么直接返回nil,所以从这里可以看出,主要是通过一个if条件来防止线程释放了其它线程加的锁。
2)如果来释放锁的线程是加锁的线程,那么就将加锁次数减1,然后拿到剩余的加锁次数 counter 变量。
3)如果counter大于0,说明有重入加锁,锁还没有彻底的释放完,那么就设置一下锁的过期时间,然后返回0
4)如果counter没大于0,说明当前这个锁已经彻底释放完了,于是就把锁对应的key给删除,然后发布一个锁已经释放的消息,然后返回1。
七、如何实现超时自动释放锁
前面我们说了不指定锁超时时间的话,那么会有看门狗线程不断的延长加锁时间,不会导致锁超时释放,自动过期。那么指定超时时间的话,是如何实现到了指定时间超时释放锁的呢?
能够设置超时自动释放锁的方法。
void lock(long leaseTime, TimeUnit unit)
boolean tryLock(long waitTime, long leaseTime, TimeUnit unit)
通过传入leaseTime参数就可以指定锁超时的时间。
无论指不指定超时时间,最终其实都会调用tryAcquireAsync方法,只不过当不指定超时时间时,leaseTime传入的是-1,也就是代表不指定超时时间,但是Redisson默认还是会设置30s的过期时间;当指定超时时间,那么leaseTime就是我们自己指定的时间,最终也是通过同一个加锁的lua脚本逻辑。
指定和不指定超时时间的主要区别是,加锁成功之后的逻辑不一样,不指定超时时间时,会开启watchdog后台线程,不断的续约加锁时间,而指定超时时间,就不会去开启watchdog定时任务,这样就不会续约,加锁key到了过期时间就会自动删除,也就达到了释放锁的目的。
所以指定超时时间达到超时释放锁的功能主要还是通过redis自动过期来实现,因为指定了超时时间,加锁成功之后就不会开启watchdog机制来延长加锁的时间。
在实际项目中,指不指定锁的超时时间是根据具体的业务来的,如果你能够比较准确的预估出代码执行的时间,那么可以指定锁超时释放时间来防止业务执行错误导致无法释放锁的问题,如果不能预估出代码执行的时间,那么可以不指定超时时间。
文章转自公众号:三友的java日记