(着重讲可重入,不同服务实例下生成的线程id和锁key一样,此时锁不支持重入)
我们在使用redisson可重入锁时,都会了解到一点:可重入是通过线程id是否相同实现的。
ps:若锁住的代码块执行完就会解锁,不会涉及到可重入性。若锁住的代码块执行完不解锁,需要延迟(过期自动释放),刚刚加锁的线程已经在等待执行下一个任务,如果此线程再次执行到该加锁代码块,即使未解锁,此线程也能再次获取到锁(重入性)。
分析下redisson获取锁的源码:
private Long tryAcquire(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
// threadId 线程id
return get(tryAcquireAsync(waitTime, leaseTime, unit, threadId));
}
private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
RFuture<Long> ttlRemainingFuture;
if (leaseTime > 0) {
// 锁固定了过期时间
ttlRemainingFuture = tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
} else {
//此处使用看门狗,默认30s过期,可通过配置修改
ttlRemainingFuture = tryLockInnerAsync(waitTime, internalLockLeaseTime,
TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
}
CompletionStage<Long> f = ttlRemainingFuture.thenApply(ttlRemaining -> {
// lock acquired
if (ttlRemaining == null) {
if (leaseTime > 0) {
internalLockLeaseTime = unit.toMillis(leaseTime);
} else {
scheduleExpirationRenewal(threadId);
}
}
return ttlRemaining;
});
return new CompletableFutureWrapper<>(f);
}
<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
return evalWriteAsync(getRawName(), LongCodec.INSTANCE, command,
"if ((redis.call('exists', KEYS[1]) == 0) " +
"or (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()),
// getLockName(threadId) 通过threadId获取锁标识。用于二次判断(可重入的关键)
unit.toMillis(leaseTime), getLockName(threadId));
}
protected String getLockName(long threadId) {
//从这里可以看到锁标识还包含了一个id,问题是这个id是如何生成的,有何作用?
return id + ":" + threadId;
}
public RedissonBaseLock(CommandAsyncExecutor commandExecutor, String name) {
super(commandExecutor, name);
this.commandExecutor = commandExecutor;
//接着往前推,能猜到一点,这个id肯定是redisson初始化连接时赋值的
this.id = commandExecutor.getConnectionManager().getId();
this.internalLockLeaseTime = commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout();
this.entryName = id + ":" + name;
}
问题已经很清晰了,接着看下redisson是如何初始化的。
我这边用的是:redisson-spring-boot-starter,入口是:org.redisson.spring.starter.RedissonAutoConfiguration#redisson()
初始化流程:
创建连接管理器,代码第一行:
UUID id = UUID.randomUUID();
往下逻辑处理的时候都会把id当作入参。所以redisson在不同服务实例下生成的id是不一样的。
Redisson获取锁,在调用getLockName(long threadId)方法时,即使线程id一样,在不同的服务实例下,返回的结果也是不相同。