Redisson(2-3)分布式锁实现对比 VS Java的ReentrantLock的FairLock

Redisson实现了一整套JDK中ReentrantLock的功能,这里对比一下公平锁(Fair)实现的差异和核心的思想。

公平锁存在的意义是为了保证绝对的公平,但是有其弊端,这个在网上有很多相关的解释,就是说绝对的公平不一定是性能最高的,因此和它相对的还有非公平锁,但是费公平锁也有问题,会引发饥饿现象。具体解释也可参见https://blog.csdn.net/xxcupid/article/details/51908916

fair模式的带超时时间的tryLock(超时时间)

ReentrantLock

ReentrantLock实现公平锁依赖的是一个叫做hasQueuedPredecessors的方法,所有的加锁操作前都要先判断这个方法的返回值,

方法的注释写的很清楚:

检验是否有其他线程已经等待了更长时间,借此保证公平。此外,注释还写道:

这两个&&判断也可以实现同样的功能,同时语义更清晰,但是效率相比之下没有前者的数据结构操作高。

总结下来,单虚拟机内部的公平操作依仗的是链表数据结构的帮助,将所有申请锁的线程按照访问的优先级排序,从而保证了这些申请锁的线程得到锁的公平性。那么对于跨虚拟机的场景要如何处理呢?类比一下,本地锁对应分布式锁,那么链表(队列)对应的就是分布式队列了,正好Redis也有对应的数据结构来支撑这个功能。

Redisson

Redisson利用了redis本身的集合操作配合lua脚本来解决这个问题,这个过程和非公平锁比起来要复杂的多。

这里会根据command分成两组,一组是EVAL_NULL_BOOLEAN,tryAcquireOnce的语义,返回的是Boolean,另一组是EVAL_LONG,返回的是Long,即当前锁的ttl,用来实现lockInterruptibly当获取锁失败时用于等待下次竞争锁的时间。

EVAL_NULL_BOOLEAN:

首先看第一段死循环,如注释所写,这段逻辑是清理已经过期的线程:

具体逻辑,首先判断存储线程的队列/列表中是否有线程存在,当列表中的头元素是空的话,说明没有过期线程,这样就可以break进行下面的锁操作逻辑。

如果队列不为空,就挨个检测存储的线程是否已经过期(过期的时间点是否已经是过去的某个时刻,即比当前时刻小),如果过期,那么将该线程从队列中移除,同时将保存该线程过期时间点的数据也从对应的队列中移除。

清理过程结束后,进行真正的锁操作逻辑:

①如果当前锁不存在,同时,存线程id的有序队列不存在或者队列存在且当前线程处于队列头(表明当前线程可以获取到锁)

②那么pop将当前threadId从队列中移除,保证下次下一个线程可以抢锁,zrem移除锁超时队列的threadId。

③threadId获取到锁,加锁,并设置重入次数为1

④对该锁设置超时时间

⑤返回nil(因为上面的指令是EVAL_NULL_BOOLEAN,所以这里返回的是true)

上面的①如果是false:

②看看当前线程是否已经获取到锁

③如果获取到,那么用hincrby将重入次数计数加一

④重新设置过期时间

⑤返回nil(因为上面的指令是EVAL_NULL_BOOLEAN,所以这里返回的是true)

EVAL_LONG:

对于EVAL_LONG,唯一不同的地方在于,当获取不到锁的时候,因为有睡眠等待的语义,所以需要返回ttl,而不是直接tryAcquireOnce返回Boolean,所以lua脚本有所不同,具体在下图描述的这一部分:

如果存储线程的优先队列中的第一个线程不是当前请求锁的线程,那么ttl就是这优先队列中第一个线程的ttl。否则,当前线程和优先队列中第一个线程是同一个线程(可以获取到锁),那么ttl就是该锁的ttl。

然后将该线程和该线程的ttl分别加入到对应的优先队列中,zadd执行返回1,表示新增成功(如果是已存在的元素,表示更新操作,返回值不是1),说明该thread之前不在队列中,此时通过rpush将其添加到存储线程的优先队列中去。

疑问:

这里有个问题还没看明白,就是这里有个threadWaitTime,设置时间是5000ms,不知道是什么作用?

看完了lua脚本再补充说几个要点:

①关于发布订阅

我们看到FairLock重载了这两个方法,和普通的锁相比,加入了threadId属性。这是为什么呢?因为这是公平锁,redis中利用优先队列保存了当前等待获取锁的所有线程,所以需要按照这些线程的优先顺序来获取锁,所以要获得下一个能获得锁的线程,只发布一个能让该线程订阅到的消息来执行操作。

猜你喜欢

转载自blog.csdn.net/xxcupid/article/details/88389828