RLock是Redisson中一种可重入的分布式锁,RLock是一个接口,同时继承了java.util.concurrent.locks.Lock
接口。
RedissonLock是RLock接口的默认实现,是一种非公平的,可重入的分布式锁。并且内部实现了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期,防止业务执行较长导致锁被自动释放。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。
Redisson版本:3.13.3
使用案例
RLock继承了java.util.concurrent.locks.Lock
接口,使用方式和Lock一样:
1 | Lock l = redissonClient.getLock("name"); |
RedissonLock源码分析
RedissonLock使用方法如下:
1 | public String test1() { |
redissonClient.getLock(“good”)
getLock默认初始化了一个RedissonLock实例
1 | /** |
RedissonLock构造方法
1 | public RedissonLock(CommandAsyncExecutor commandExecutor, String name) { |

id是一个UUID,用于保证自己持有的锁不会被其他线程解锁,因为用的同一个commandExecutor对象,所以这里不会重复生成UUID。

internalLockLeaseTime是锁的超时时间,默认是30s。

lock方法
加锁
leaseTime表示锁的超时时间,对应Redis中key的过期时间
1 |
|
tryAcquire方法
尝试加锁,如果加锁失败则返回锁还有多少毫秒过期,加锁成功则返回空
1 | private Long tryAcquire(long waitTime, long leaseTime, TimeUnit unit, long threadId) { |
lua脚本 加锁
这里使用了lua脚本来保证加锁操作的原子性
1 | if (redis.call('exists', KEYS[1]) == 0) then |

- 如果锁不存在,则创建锁(Hash结构)。然后使用
hincrby
命令(如果key不存在,value则为0),相当于往这个Hash中添加:Key为”uuid:线程id“,value为”1“(key使用uuid+线程id是为了保证不被其他线程解锁,value则表示锁的重入次数)。然后设置锁Hash的过期时间,返回null。 - 如果锁已存在并且value存在于Hash中,说明是自己加的锁,则将锁的重入次数+1,然后设置锁的过期时间,返回null。
- 锁存在,但不是自己创建的,则返回锁还有多少毫秒过期。
Watch Dog 续锁
设置定时任务每隔1/3的过期时间进行续锁,解锁时也会取消续锁。
1 | private void scheduleExpirationRenewal(long threadId) { |
定时任务-netty时间轮
- 时间轮是一种高效利用线程资源来进行批量化调度的一种调度模型(调度任务全部都绑定到同一个的调度器)。
- 时间轮调度器的时间精度可能不是很高,取决于时间段“指针”单元的最小粒度大小。
- 串行执行,所有的任务是依次执行,容易出现调度超时和任务堆集的情况。
- 无法再宕机之后恢复任务重新调度。
1 | public class MasterSlaveConnectionManager implements ConnectionManager { |
subscribe方法
订阅锁释放事件,设置监听器
1 | protected RFuture<RedissonLockEntry> subscribe(long threadId) { |
tryLock方法
尝试加锁,设置最大等待时间
1 |
|
unLock方法
解锁,
1 |
|
lua脚本 解锁
1 | if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then |
- 如果锁不存在,或者不是自己的锁,则返回空。
- 如果锁的重入次数大于1,则重新设置锁的过期时间,返回0。
- 如果锁的重入次数等于1(不可能小于1),则删除锁,并且发布锁释放事件,返回1。
引出思考
- 为啥不先使用JVM的锁尝试加锁,加锁成功后再操作Redis,减少网络IO次数。
- 重入次数感觉没必要存入Redis,直接用ReentrantLock代替或者本地变量代替即可。