1. 测试代码,以黑马点评项目P67视频为基础
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@Slf4j
@SpringBootTest
class RedissonTest {
@Resource
private RedissonClient redissonClient;
private RLock lock;
@BeforeEach
void setUp() {
lock = redissonClient.getLock("order");
}
@Test
void method1() throws InterruptedException {
// 尝试获取锁
boolean isLock = lock.tryLock(1L, TimeUnit.SECONDS); //锁的过期时间默认为30s
if (!isLock) {
log.error("获取锁失败 .... 1");
return;
}
try {
log.info("获取锁成功 .... 1");
method2();
log.info("开始执行业务 ... 1");
} finally {
log.warn("准备释放锁 .... 1");
lock.unlock();
}
}
void method2() {
// 尝试获取锁
boolean isLock = lock.tryLock();
if (!isLock) {
log.error("获取锁失败 .... 2");
return;
}
try {
log.info("获取锁成功 .... 2");
log.info("开始执行业务 ... 2");
} finally {
log.warn("准备释放锁 .... 2");
lock.unlock();
}
}
}
2. 锁重试运行逻辑分析
点击查看lock.tryLock(1L, TimeUnit.SECONDS);的源码实现
在Long ttl=tryAcquire(waitTime,leaseTime,unit,threadId);尝试获取锁,开始进行获取锁的业务逻辑,点击进入tryAcquire函数
在tryAcquire函数中调用了tryAcquireAsync函数,下面为tryAcquireAsync函数
随后执行TryLockInnerAsync函数,通过执行lua脚本来保证原子性获取锁
lua脚本解释:
- 判断锁是否存在,若不存在则设置锁,并将value设为1,设置过期时间
- 如果锁存在,判断是否是自己的锁,若是,则将value+1,重置过期时间
- 若不是自己的锁,则锁的剩余过期时间
该函数返回null(获取锁成功)或者锁的剩余过期时间,在tryLock函数中会进行判断,如果为null,则返回true
否则,则获取当前时间,订阅锁过期通知,订阅等待时间是有限的(值为剩余等待时间)
当在规定时间等到了锁过期通知,则会执行以下部分,仍会进行锁等待时间的判断
之后便进行获取锁的重试
业务流程图分析如下:
3. 锁超时运行逻辑分析
当线程1业务因为阻塞,导致锁超时释放,而线程2拿到了该锁,当线程1业务执行完毕,释放锁,会导致线程2的锁丢失,其他线程可以去获取锁
在执行ttlRemainingFuture.onComplete时,ttlRemaining表示剩余有效期,e表示异常
当剩余有效期为null的时候,执行scheduleExpirationRenewal函数释放锁,下面为scheduleExpirationRenewal函数
该函数会根据线程Id,进行创建锁,如果不是第一次进入,则更新有效期。在renewExpiration()函数中执行更新有效期,最终会执行lua脚本
全流程的流程图如下图所示: