redLock也是Redis提供的一个分布式锁,和redissonLock有些区别
RedLock可以指定等待时间,也就是说,假如我指定了等待时间waitTime是2S,比如:
1.A线程来加锁,正常去执行业务逻辑
2.B线程也来加锁,此时会加锁失败,那B线程最多等待2S,如果超过了2S还没有获取到分布式锁,那B线程加锁就返回false,表示加锁失败
redLock的思想是:
还有一个点:不管是redLock,还是redissonLock,两者底层都是通过相同的lua脚本来加锁、释放锁的,所以,两者只是外部形态的不同,底层是一样的
redLock是继承了redissonMultiLock,大部分的逻辑,都是在redissonMultiLock中去实现的,所以源码部分,大部分都是RedissonMultiLock
org.redisson.RedissonMultiLock#tryLock(long, long, java.util.concurrent.TimeUnit)
在加锁的时候,会调用到这个方法,这里入参的意思分别是:
第一个waitTime是锁等待时间
leaseTime是锁加锁时间
TimeUnit是时间单位
我的理解是:线程A在加锁的时候,会使用leaseTime作为锁失效时间,如果没有指定,就是默认30S,然后进行锁续期;如果指定了,那就使用指定的阈值,并且不会自动化续期
线程B来加锁的时候,如果线程A已经对同一个key进行了加锁,那线程B最多等待waitTime时间,如果到了这个时间,线程B依旧没有获取到锁,那线程B加锁的方法就会返回false,表示加锁失败
一、最外层逻辑判断
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
long newLeaseTime = -1;
// 1.这里是在指定了加锁时间,会将新的加锁时间设置为锁等待时间的2倍;这里我没怎么理解为什么要这么做,这里也是之前踩过的一个坑
if (leaseTime != -1) {
newLeaseTime = unit.toMillis(waitTime)*2;
}
long time = System.currentTimeMillis();
long remainTime = -1;
// 2.如果waitTime不等于-1,就是指定了等待时间的场景下,将等待时间转换为毫秒
if (waitTime != -1) {
remainTime = unit.toMillis(waitTime);
}
// 3.这里的计算,会用remainTime / Redis机器的数量;所以:如果我指定的等待时间是2000ms,那这里获取到的lockWaitTime就是666ms
long lockWaitTime = calcLockWaitTime(remainTime);
int failedLocksLimit = failedLocksLimit();
List<RLock> acquiredLocks = new ArrayList<RLock>(locks.size());
// 4.对所有的Redis节点进行遍历,然后依次去加锁,如果加锁失败的话,会进行一些列的处理;这里加锁失败的处理逻辑我还没有细看,后面再补充
for (ListIterator<RLock> iterator = locks.listIterator(); iterator.hasNext();) {
RLock lock = iterator.next();
boolean lockAcquired;
try {
// 4.1 如果等待时间未指定、锁超时时间未指定,那就调用下面这个方法即可,这个方法就和redissonLock加锁是一样的
if (waitTime == -1 && leaseTime == -1) {
lockAcquired = lock.tryLock();
} else {
// 4.2 反之,就会调用入参了waitTime和leaseTime的方法
long awaitTime = Math.min(lockWaitTime, remainTime);
lockAcquired = lock.tryLock(awaitTime, newLeaseTime, TimeUnit.MILLISECONDS);
}
} catch (RedisResponseTimeoutException e) {
unlockInner(Arrays.asList(lock));
lockAcquired = false;
} catch (Exception e) {
lockAcquired = false;
}
if (lockAcquired) {
acquiredLocks.add(lock);
} else {
if (locks.size() - acquiredLocks.size() == failedLocksLimit()) {
break;
}
if (failedLocksLimit == 0) {
unlockInner(acquiredLocks);
if (waitTime == -1 && leaseTime == -1) {
return false;
}
failedLocksLimit = failedLocksLimit();
acquiredLocks.clear();
// reset iterator
while (iterator.hasPrevious()) {
iterator.previous();
}
} else {
failedLocksLimit--;
}
}
if (remainTime != -1) {
remainTime -= (System.currentTimeMillis() - time);
time = System.currentTimeMillis();
if (remainTime <= 0) {
unlockInner(acquiredLocks);
return false;
}
}
}
if (leaseTime != -1) {
List<RFuture<Boolean>> futures = new ArrayList<RFuture<Boolean>>(acquiredLocks.size());
for (RLock rLock : acquiredLocks) {
RFuture<Boolean> future = rLock.expireAsync(unit.toMillis(leaseTime), TimeUnit.MILLISECONDS);
futures.add(future);
}
for (RFuture<Boolean> rFuture : futures) {
rFuture.syncUninterruptibly();
}
}
return true;
}
上面这段代码,是redLock加锁的外层逻辑,有几个点需要注意:
二、针对waitTime和leaseTime的逻辑
这个是上面4.2处注解对应的方法
// 这是加锁的核心逻辑,这里我们不管前面传过来的加锁时间等待时间是多少,我们就认为等待时间是2S,加锁时间是5S
@Override
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
// 1.这里的这个time就是当前加锁的线程如果加锁失败,最多等待的时间;获取到当前时间、当前线程ID
long time = unit.toMillis(waitTime);
long current = System.currentTimeMillis();
final long threadId = Thread.currentThread().getId();
// 2.这里就是尝试加锁的逻辑,和redissonLock加锁调用的是同一个方法,就不做过多注释;如果返回的ttl不为null,就表示加锁失败
Long ttl = tryAcquire(leaseTime, unit, threadId);
// lock acquired
if (ttl == null) {
return true;
}
// 3.在第一次加锁失败之后,会判断等待时间是否超过了阈值;如果超过了,就是说等待时间超过了2S,就返回false
time -= (System.currentTimeMillis() - current);
if (time <= 0) {
acquireFailed(threadId);
return false;
}
// 4.如果不超过阈值,就订阅对应的channel
current = System.currentTimeMillis();
final RFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);
// 4.1 这里是调用了await方法,跳进去,发现调用的是countDownLatch.await()方法,总之如果await失败,会进行一系列的处理
if (!await(subscribeFuture, time, TimeUnit.MILLISECONDS)) {
if (!subscribeFuture.cancel(false)) {
subscribeFuture.addListener(new FutureListener<RedissonLockEntry>() {
@Override
public void operationComplete(Future<RedissonLockEntry> future) throws Exception {
if (subscribeFuture.isSuccess()) {
unsubscribe(subscribeFuture, threadId);
}
}
});
}
acquireFailed(threadId);
return false;
}
try {
// 4.2 在await了之后,再次判断是否超过了等待时间
time -= (System.currentTimeMillis() - current);
if (time <= 0) {
acquireFailed(threadId);
return false;
}
// 5.如果依旧没有达到阈值,就进行尝试加锁,如果加锁成功,则返回true
// 如果加锁失败,在time的基础之上,再减去加锁的耗时
while (true) {
long currentTime = System.currentTimeMillis();
ttl = tryAcquire(leaseTime, unit, threadId);
// lock acquired
if (ttl == null) {
return true;
}
time -= (System.currentTimeMillis() - currentTime);
if (time <= 0) {
acquireFailed(threadId);
return false;
}
// waiting for message
// 6.代码执行到这里,表示依旧没有超过等待的阈值;这里的getLatch()获取到的是semaphore的一个实现类,会调用其UNSAFE.park(false, nanos);方法
currentTime = System.currentTimeMillis();
if (ttl >= 0 && ttl < time) {
getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
} else {
getEntry(threadId).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
}
time -= (System.currentTimeMillis() - currentTime);
if (time <= 0) {
acquireFailed(threadId);
return false;
}
}
} finally {
unsubscribe(subscribeFuture, threadId);
}
// return get(tryLockAsync(waitTime, leaseTime, unit));
}
根据这段代码的注释,就可以看出来,在加锁的时候,多了一个waitTime的判断,也就是说,如果线程B等待的时候,超过了waitTime,就直接return false, 加锁失败,而不是像redissonLock那样,不停的休眠、重试加锁
释放锁的逻辑和redissonLock释放锁的逻辑几乎上是一样的,只是redLock外层多了一个for循环而已,循环所有的Redis节点,分别去每个节点释放锁
总结下,redLock和redissonLock两者的区别