【本篇文章基于redisson-3.17.6版本源码进行分析】
通过前面对Redisson可重入锁的LUA脚本分析,我们知道,当执行LUA脚本返回nil的时候,对应java就是返回null,表示当前线程获取分布式锁成功,主要有三种情况:
- 1、当前锁对应的key在redis不存在,直接加锁,返回nil,加锁成功;
- 2、当前锁对应的key存在,并且hash key就是当前线程自己,允许重入,返回nil,加锁成功;
- 3、当前锁对应的key存在,但是hash key是其它线程,获取锁失败,此时返回的是锁剩余过期时间;
Redisson可重入锁实现互斥的关键,就是上述的第三种情况,获取锁失败后,返回锁剩余的过期时间。对应的LUA脚本如下图框起来的部分:
也就是tryLockInnerAsync()加锁方法返回的是锁剩余过期时间,tryAcquire()也是返回的锁剩余过期时间。
private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
// 当前线程ID
long threadId = Thread.currentThread().getId();
// tryAcquire()尝试获取锁
Long ttl = tryAcquire(-1, leaseTime, unit, threadId);
// lock acquired
// ttl为空表示获取锁成功,则直接返回
if (ttl == null) {
return;
}
// 未能获取到锁的其它线程
// 争抢同一把锁的其它线程,订阅channel消息
// 发布-订阅消息:当释放锁的时候,会通过publish发布一条消息,通知其它等待这个锁的线程,我已经释放锁了,你们可以过来获取了。
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);
// lock acquired
if (ttl == null) {
break;
}
// waiting for message
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);
}
// get(lockAsync(leaseTime, unit));
}
如上代码,可以看到,lock()方法加锁成功后,会返回nil,此处会判断为 null,直接返回。
当获取锁失败之后,首先会通过subscribe()订阅【"redisson_lock__channel" + 锁key】的消息,也就是订阅锁释放的消息,当前持有锁的线程在释放锁之后,会通过publish指令发布一条消息,这样之前获取锁失败的那些线程就知道锁已经被释放了,可以获取了。
除了订阅消息之外,最关键的还是通过while(true)自旋,不断调用tryAcquire()方法尝试去获取锁,直到加锁成功,才break。如下图: