目录
1、Redisson lock
方法原理解析
1. 如果指定了过期时间
- 异步续命机制(Watchdog 机制)不再生效,锁会在指定的时间过期并自动释放。
2. 如果没有指定过期时间
- 启动 Watchdog 机制,自动续命锁,直到显式调用
unlock()
方法释放锁为止。
3. lock
方法的主要步骤
以下是 Redisson lock
方法的完整代码及其详细分步骤解释。
Redisson lock
方法完整代码
private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
long threadId = Thread.currentThread().getId(); // 获取当前线程ID
Long ttl = tryAcquire(-1, leaseTime, unit, threadId); // 尝试获取锁,等待时间为-1,表示无限等待
if (ttl == null) { // 如果成功获取到锁,直接返回
return;
}
CompletableFuture<RedissonLockEntry> future = subscribe(threadId); // 订阅锁释放通知
pubSub.timeout(future); // 设置超时回调
RedissonLockEntry entry;
if (interruptibly) {
entry = commandExecutor.getInterrupted(future); // 获取可中断的锁条目
} else {
entry = commandExecutor.get(future); // 获取锁条目
}
try {
while (true) {
ttl = tryAcquire(-1, leaseTime, unit, threadId); // 尝试重新获取锁
if (ttl == null) { // 如果成功获取到锁,退出循环
break;
}
if (ttl >= 0) {
try {
entry.getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS); // 等待锁释放通知
} catch (InterruptedException e) {
if (interruptibly) {
throw e;
}
entry.getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS); // 再次等待锁释放通知
}
} else {
if (interruptibly) {
entry.getLatch().acquire(); // 等待锁释放通知
} else {
entry.getLatch().acquireUninterruptibly(); // 等待锁释放通知,不可中断
}
}
}
} finally {
unsubscribe(entry, threadId); // 取消订阅
}
}
分步骤解释
步骤 1:尝试获取锁
- 方法调用:
tryAcquire(-1, leaseTime, unit, threadId)
- 解释:
- 使用 Lua 脚本尝试原子性地获取锁。
- 如果锁不存在,创建新锁并设置过期时间。
- 如果锁存在并且由当前线程持有,增加锁的重入计数并重新设置过期时间。
- 如果成功获取到锁,返回
null
,否则返回锁的剩余存活时间。
private Long tryAcquire(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
return evalWrite(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_LONG,
"if (redis.call('exists', KEYS[1]) == 0) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
"return redis.call('pttl', KEYS[1]);",
Collections.singletonList(getRawName()), unit.toMillis(leaseTime), getLockName(threadId));
}
-
Lua 脚本原理
-
检查锁是否存在:
if (redis.call('exists', KEYS[1]) == 0) then redis.call('hincrby', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end;
- 如果锁不存在(
exists
返回 0),则创建一个新的锁,并将其设置为当前线程持有,同时设置过期时间。
- 如果锁不存在(
-
检查锁是否由当前线程持有:
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then redis.call('hincrby', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end;
- 如果锁已经存在并且由当前线程持有(
hexists
返回 1),则增加锁的重入计数,并重新设置过期时间。
- 如果锁已经存在并且由当前线程持有(
-
返回锁的剩余存活时间:
return redis.call('pttl', KEYS[1]);
- 如果锁存在且不由当前线程持有,则返回锁的剩余存活时间。
-
步骤 2:获取锁失败,发起订阅
如果初次尝试获取锁失败,Redisson 会订阅锁的释放通知。
- 方法调用:
subscribe(threadId)
- 解释:
- 如果初次尝试获取锁失败,Redisson 会订阅锁的释放通知。
- 通过
subscribe
方法订阅锁的释放通知,以便在锁被释放时能够及时收到通知。 pubSub.timeout(future)
设置超时回调,以防订阅过程中出现问题。- 使用
commandExecutor.get
或commandExecutor.getInterrupted
获取订阅结果,根据是否可中断进行选择。
CompletableFuture<RedissonLockEntry> future = subscribe(threadId);
pubSub.timeout(future);
RedissonLockEntry entry;
if (interruptibly) {
entry = commandExecutor.getInterrupted(future);
} else {
entry = commandExecutor.get(future);
}
步骤 3:循环等待锁释放和尝试获取锁
在等待锁释放期间,Redisson 会进入一个循环,不断尝试重新获取锁。