在上篇中,我们基于spring boot整合redisson实现了分布式锁,接下来我会带领大家花一些时间来学习redisson如何实现各种锁,所以我们需要先从github上下载它的源码,本篇则先从可重入锁的相关实现开始来为大家做讲解。
这里我们按照步骤逐步分析Redisson 可重入锁的加锁流程。
// RLock 接口的默认实现类 RedissonLock
public void lock() {
try {
lock(-1, null, false);
} catch (InterruptedException e) {
throw new IllegalStateException();
}
}
private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
// 获取当前线程ID
long threadId = Thread.currentThread().getId();
// 尝试获取锁
Long ttl = tryAcquire(leaseTime, unit, threadId);
// 如果ttl为空,表示获取锁成功
if (ttl == null) {
return;
}
// 如果获取锁失败,订阅到对应的redisson锁channel,等待锁释放消息
RFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);
if (interruptibly) {
subscribeFuture.syncUninterruptibly();
} else {
subscribeFuture.sync();
}
try {
while (true) {
// 再次尝试获取锁
ttl = tryAcquire(leaseTime, unit, threadId);
// 成功获取锁,直接返回
if (ttl == null) {
break;
}
// 等待锁释放通知
if (ttl >= 0) {
try {
await(ttl, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
if (interruptibly) {
throw e;
}
}
}
}
} finally {
// 取消订阅
unsubscribe(subscribeFuture, threadId);
}
}
private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) {
// 根据传入的租约时间计算锁的过期时间
long currentTime = System.currentTimeMillis();
Long ttl = null;
// 如果指定了租约时间
if (leaseTime != -1) {
ttl = tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
}
// 使用默认的过期时间
else {
ttl = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(),
TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
// 启动看门狗定时续期
scheduleExpirationRenewal(threadId);
}
return ttl;
}
继续看tryLockInner
方法 - 它是最核心的加锁 Lua 脚本:
<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
// 这里执行 Lua 脚本
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
// 判断锁是否存在
"if (redis.call('exists', KEYS[1]) == 0) then " +
// 不存在则创建锁,并设置重入次数为1
"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 " +
// 是当前线程,则重入次数+1
"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(getName()), // KEYS[1] 锁名称
unit.toMillis(leaseTime), // ARGV[1] 锁过期时间
getLockName(threadId)); // ARGV[2] 线程标识
}
它的核心逻辑也很简单:首先检查锁是否存在,如果不存在,则直接加锁,且设置重入次数为1;如果存在,先检查是否是当前线程的锁,如果是,则重入次数+1,如果不是,则返回锁的剩余过期时间。
本篇剖析了redisson可重入锁的加锁流程源码,其实这里读者应该可以发现我们前面通过redis手撸的时候的逻辑其实和这里几乎一致,这也是我们学习源码的意义,借鉴别人优秀的设计并为自己所用!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。