在Redis分布式锁:redissonLock和RedLock底层都有加锁失败的场景处理,比如:A线程在加锁失败之后,会返回当前已经加锁的线程B还有多久到期,然后线程A就会休眠指定的时间,然后再进行加锁
这里自己在学习的时候,有一个点,一直没注意,就是Redis分布式锁在休眠的时候,是通过什么机制实现的?
在Redis分布式锁之:RedissonLock这篇博客中,有介绍加锁的具体流程,所以,这里就不再重复了,直接戳到核心代码里面来看
加锁失败的代码
@Override
public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {
long threadId = Thread.currentThread().getId();
Long ttl = tryAcquire(leaseTime, unit, threadId);
// lock acquired
if (ttl == null) {
return;
}
RFuture<RedissonLockEntry> future = subscribe(threadId);
commandExecutor.syncSubscription(future);
try {
while (true) {
ttl = tryAcquire(leaseTime, unit, threadId);
// lock acquired
if (ttl == null) {
break;
}
// waiting for message
/**
* 这里在看redisson-3.6.5的时候,发现这里的逻辑,有一个if else 的判断,在早期的版本中,这里没有ttl < 0 的场景
* 所以这里的else逻辑我要再学习一下,后面再补充
*/
if (ttl >= 0) {
getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
} else {
getEntry(threadId).getLatch().acquire();
}
}
} finally {
unsubscribe(future, threadId);
}
}
这是redissonLock 加锁失败的源码,redLock加锁失败的逻辑和这个大差不差,只是在获取这个ttl的时候,有一个区别:
对于redissonLock, ttl就是当前锁还有多久过期,比如还有4s过期,那ttl就是4s
对于redLock, 如果锁还有4S过期,但是waitTime还剩下3s,那ttl就是3S;反之,如果waitTime还有5S,但是锁还有3s就过期,那ttl就是3S
我们以redissonlock为例,在获取到还有X毫秒失效之后,会调用getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
这里需要看下getLatch()获取到的对象是什么:
org.redisson.RedissonLockEntry 在这个类中,会初始化latch对象,就是一个private final Semaphore latch;对象
public RedissonLockEntry(RPromise<RedissonLockEntry> promise) {
super();
this.latch = new Semaphore(0);
this.promise = promise;
}
Semaphore
通过Semaphore来实现?如何实现?
有了解过相关源码的,应该知道,Semaphore是用来进行资源竞争的一个工具类,初始化时,指定的permits表示同时允许有几个线程持有资源
redissonlock在初始化Semaphore时,指定permits为0,这里为0,就表示永远不允许有线程持有资源
那这样的话,在tryAcquire()时,只需要指定超时时间为key的过期时间即可,这样的话,就会在过期时间到了之后,从条件队列进入到同步等待队列,然后加锁获取资源
java.util.concurrent.Semaphore#tryAcquire(long, java.util.concurrent.TimeUnit)
java.util.concurrent.locks.AbstractQueuedSynchronizer#tryAcquireSharedNanos
java.util.concurrent.Semaphore.FairSync#tryAcquireShared
java.util.concurrent.locks.AbstractQueuedSynchronizer#doAcquireSharedNanos
上面这个链路,就和redisson没有关系了,完全是aqs的代码
private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
// nanosTimeout是要等待的时长
if (nanosTimeout <= 0L)
return false;
// 获取到deadline
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return true;
}
}
nanosTimeout = deadline - System.nanoTime();
// 执行完上面的逻辑,再校验一遍,是否超时,如果超时,就无需休眠
if (nanosTimeout <= 0L)
return false;
// 如果不超时,就根据最新的nanosTimeout 进行休眠
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
这里的逻辑,可以看到,底层就是通过lockSupport的park和unPark()方法来实现休眠和唤醒