Redisson的可重入锁(Reentrant Lock)是基于Redis实现的分布式锁,用于在分布式系统中提供线程安全的锁机制。它允许同一个线程在不释放锁的情况下多次获得锁,并在所有锁操作完成后,锁才真正被释放。下面我们来详细解析Redisson可重入锁的原理。
基本原理
可重入锁的核心思想是,同一线程可以多次获取同一个锁,且只有当所有锁释放操作都完成后,锁才会真正释放。
锁的可重入特性
Redisson通过维护一个计数器来实现锁的可重入特性。
- 当同一个线程第一次获取锁时,Redis会记录下这个线程的
<threadId>
,并将锁的持有次数设置为1。 - 如果这个线程再次请求锁(即可重入操作),Redisson会检测到当前持有锁的
<threadId>
与当前线程相同,则不会重新设置锁,而是简单地增加计数器,表示这个线程再次持有了锁。 - 每次释放锁时,Redisson会减少计数器,只有当计数器减为0时,锁才会真正释放。
锁的释放操作
锁的释放操作需要确保只有持有锁的线程才能释放锁。Redisson会在释放锁时检查Redis中存储的<threadId>
与当前线程的ID是否匹配,确保线程安全。
释放锁的Lua脚本逻辑:
-- 如果Redis中存储的锁的持有者与当前线程的标识符相等(ARGV[1]),则进行解锁操作
if redis.call('get', KEYS[1]) == ARGV[1] then
-- 尝试递减计数器(KEYS[2]为计数器的Key),表示释放一次锁
if redis.call('decr', KEYS[2]) == 0 then
-- 如果计数器减为0,说明锁完全释放,删除锁的Key
return redis.call('del', KEYS[1])
else
-- 如果计数器未减为0,返回0表示锁未完全释放,暂时不删除锁
return 0
end
else
-- 如果当前线程标识不匹配,说明不是该线程持有的锁,返回0表示无法解锁
return 0
end
逻辑:如果当前线程ID与Redis中保存的线程ID相同,则检查计数器的值。如果计数器减为0,删除锁;否则,只减少计数器值。
关键机制
锁的自动过期
为了防止死锁,Redisson为锁设置了过期时间。如果持有锁的线程因为意外情况(如崩溃、网络分区等)未能及时释放锁,Redis会在锁的过期时间后自动释放,防止锁长时间占用资源。
Watchdog机制(看门狗)
Redisson提供了Watchdog机制,用于动态延长锁的有效期。默认情况下,Redisson的锁在过期之前,会通过看门狗定期检查线程的状态:
- 如果线程仍然在持有锁(即线程还在运行且没有释放锁),看门狗会自动延长锁的过期时间,防止锁过早释放。
- 默认的看门狗延时是
30秒
,Redisson会在锁的持有期间不断续期。
Lua脚本保证原子性
Redisson使用了Redis的Lua脚本来执行锁的加锁、释放等操作。Lua脚本在Redis中具有原子性,这意味着即使在分布式环境下,这些操作也不会受到并发影响,从而保证了锁的安全性和一致性。
Redisson 可重入锁的代码示例
RLock lock = redissonClient.getLock("myLock");
try {
// 尝试加锁,最多等待10秒,上锁以后20秒自动解锁
boolean isLocked = lock.tryLock(10, 20, TimeUnit.SECONDS);
if (isLocked) {
// 执行业务逻辑
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock(); // 释放锁
}
}
MultiLock
MultiLock
原理解析
-
多锁合并:
MultiLock
包含多个RLock
实例。它会尝试同时获取这些锁,只有当所有锁都成功获取时,才算加锁成功。 -
加锁过程:
- 当调用
lock()
方法时,MultiLock
会依次尝试获取多个RLock
(即多个 Redis 实例上的锁),并且每个锁都有独立的超时时间。 - 如果某一个锁获取失败,
MultiLock
会回滚已经获取的锁,确保加锁操作的原子性——要么全部加锁成功,要么加锁失败,并释放已获得的锁。
- 当调用
-
解锁过程:
- 当所有锁都被成功获取并且业务处理完成时,
unlock()
方法会逐个释放这些锁。解锁时同样遵循先加锁的顺序。 - 如果某个锁释放失败,系统会继续尝试释放其他锁,确保整体资源的一致性。
- 当所有锁都被成功获取并且业务处理完成时,
-
可重入性:
MultiLock
是可重入的,如果一个线程已经持有某些资源的锁,它可以再次申请这些锁,而不会阻塞。这是通过 Redis 的可重入锁机制实现的,类似于单个RLock
的可重入锁。
-
超时和看门狗机制:
- 和普通的
Redisson
锁一样,MultiLock
也可以配置超时时间,并通过Redisson
的看门狗机制自动续期,以防止锁在任务尚未完成时被意外释放。
- 和普通的
具体实现过程
-
加锁请求:当客户端尝试加锁时,
MultiLock
会向多个 Redis 实例发送加锁请求。每个加锁请求带有一个唯一标识符(通常为线程ID),并设置一个过期时间来防止死锁。 -
全成功条件:只有当所有的加锁请求都成功(即多个
RLock
都被锁定),MultiLock
才算是成功加锁。如果任何一个锁失败,则需要释放已经成功的锁。 -
释放锁:释放锁时,同样会发送多个解锁请求,只有当所有锁都被释放时,才算成功解锁。