- 创建Redisson
这里创建了一个单体的Redisson,集群创建方式参考redisson官网
@Value("${spring.redis.host}")
String host;
@Value("${spring.redis.port}")
String port;
@Value("${spring.redis.password}")
String password;
@Bean
public RedissonClient redisson() {
Config config = new Config();
SingleServerConfig serverConfig = config.useSingleServer().setAddress("redis://" + host + ":" + port);
if (StringUtils.isNotBlank(password)) {
serverConfig.setPassword(password);
}
config.useSingleServer().setConnectionMinimumIdleSize(5);
RedissonClient client = Redisson.create(config);
return client;
}
- 获取并创建锁
使用@Resource注解引入RedissonClient
@Resource
RedissonClient redissonClient;
然后调用redissonClient的getLock方法,传入锁的名称,名称任意编写,没有特殊要求
RLock lock = redissonClient.getLock("lock");
lock.lock();
记得使用完了之后unlock()一下
lock.unlock();
这里就获取并创建了一个分布式锁。
分布式锁原理:
redisson是基于了redis做的一个分布式锁,使用了类似redis的set key value nx命令的脚本,做的一个原子性建锁操作,而set key value ExpirationTime NX,重点在于它的NX,这个关键字的意思就是,如果锁不存在,则设置锁,并返回1(Long类型),如果锁存在,这返回0,锁存在,就代表着,有线程获取到了锁,并正在执行任务,其他的线程,会进入阻塞状态,在外部等待。
而redisson有一个特点,就是在我们不设置过期时间时,会自动设置一个默认的30s过期时间 this.lockWatchdogTimeout = 30000L;
并且,如果线程执行的任务时间,超过了 30000L / 3L ,看门狗就会将锁的超时时间,自动重新续期到30s,直到任务执行结束。
而任务如果在执行途中,程序死掉了,没有主动触发unlock()方法,也不会造成死锁,看门狗在线程结束的之后,监测到锁没有被释放,就会在30s之后自动释放锁,解决了死锁问题,
lock()无参方法源码解析
public void lock() {
try {
this.lock(-1L, (TimeUnit)null, false);
} catch (InterruptedException var2) {
throw new IllegalStateException();
}
}
进入lock方法之后,会调用内部的重构的lock方法,三个参数为,等待时间(-1为永久),超时时间(注意这里传入了一个null),是否可中断
是否可中断的意思是,如果有线程获取到了锁,则其他的线程不进入阻塞状态,直接获取锁失败,比如A线程获取到了锁,这是B线程也来获取锁,会进入阻塞状态,而lock的interruptibly参数设置为了true,B线程就会调用syncSubscriptionInterrupted()中断阻塞状态。
private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
//获取到当前线程id
long threadId = Thread.currentThread().getId();
//尝试获取锁(看下一个方法详解)
Long ttl = this.tryAcquire(-1L, leaseTime, unit, threadId);
if (ttl != null) {
RFuture<RedissonLockEntry> future = this.subscribe(threadId);
if (interruptibly) {
this.commandExecutor.syncSubscriptionInterrupted(future);
} else {
this.commandExecutor.syncSubscription(future);
}
try {
//自旋重试获取锁
while(true) {
ttl = this.tryAcquire(-1L, leaseTime, unit, threadId);
if (ttl == null) {
return;
}
if (ttl >= 0L) {
try {
((RedissonLockEntry)future.getNow()).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
} catch (InterruptedException var13) {
if (interruptibly) {
throw var13;
}
((RedissonLockEntry)future.getNow()).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
}
} else if (interruptibly) {
((RedissonLockEntry)future.getNow()).getLatch().acquire();
} else {
((RedissonLockEntry)future.getNow()).getLatch().acquireUninterruptibly();
}
}
} finally {
this.unsubscribe(future, threadId);
}
}
}
tryAcquire方法解析
private Long tryAcquire(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
//调用异步获取锁方法,tryAcquireAsync(此方法是真正的获取锁的方法)
return (Long)this.get(this.tryAcquireAsync(waitTime, leaseTime, unit, threadId));
}
tryAcquireAsync方法解析
private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
RFuture ttlRemainingFuture;
//如果laseTime不为-1,则表示我们自定义了过期时间,那么锁将不会自动延期,在设置锁时,就会把过期时间设置上,到期redis自动删除
if (leaseTime != -1L) {
ttlRemainingFuture = this.tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
} else {
//否则,过期时间传入默认的,this.internalLockLeaseTime,这个参数是公共属性,在构造RedissonLock对象时进行设置,参考下图
ttlRemainingFuture = this.tryLockInnerAsync(waitTime, this.internalLockLeaseTime, TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
}
//获取锁成功
ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
//如果e==null,则表示获取锁的过程中没有出现bug
if (e == null) {
//ttlRemaining ==null则表示没有锁,可以进行设置
if (ttlRemaining == null) {
//leaseTime != -1L就表示用户自己设置了过期时间,不需要进行看门狗调度延期,到期自动删除
if (leaseTime != -1L) {
this.internalLockLeaseTime = unit.toMillis(leaseTime);
} else {
//如果用户没有设置过期时间,则进行调度延期(看门狗实现锁超时时间自动延长)
//调度方法详解,看下面**scheduleExpirationRenewal详解**
this.scheduleExpirationRenewal(threadId);
}
}
}
});
return ttlRemainingFuture;
}
scheduleExpirationRenewal详解
//调度延期
protected void scheduleExpirationRenewal(long threadId) {
//获取要延期的锁信息集合Map
RedissonBaseLock.ExpirationEntry entry = new RedissonBaseLock.ExpirationEntry();
//this就是调用scheduleExpirationRenewal的RedissonLock对象,通过锁名称,获取到要延期的锁信息
//第一次获取肯定是空的,则会进入else
//重点在于renewExpiration()方法,这个方法才是延期的方法
RedissonBaseLock.ExpirationEntry oldEntry = (RedissonBaseLock.ExpirationEntry)EXPIRATION_RENEWAL_MAP.putIfAbsent(this.getEntryName(), entry);
if (oldEntry != null) {
oldEntry.addThreadId(threadId);
} else {
entry.addThreadId(threadId);
this.renewExpiration();
}
}
renewExpiration()方法详解
private void renewExpiration() {
//拿到需要延期的锁信息
RedissonBaseLock.ExpirationEntry ee = (RedissonBaseLock.ExpirationEntry)EXPIRATION_RENEWAL_MAP.get(this.getEntryName());
//如果锁信息不为空
if (ee != null) {
//构建一个定时任务,周期为 this.internalLockLeaseTime / 3L,而this.internalLockLeaseTime就是上面提到的默认超时时间30000L,也就是30s,30000L / 3L,得出周期时间为,10秒,也就是说,10秒检查一次,每次都会将锁的过期时间延长至30秒
Timeout task = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
public void run(Timeout timeout) throws Exception {
//获取要延期的锁信息
RedissonBaseLock.ExpirationEntry ent = (RedissonBaseLock.ExpirationEntry)RedissonBaseLock.EXPIRATION_RENEWAL_MAP.get(RedissonBaseLock.this.getEntryName());
//锁存在才继续,否则退出
if (ent != null) {
Long threadId = ent.getFirstThreadId();
//线程id不为空才继续,否则退出
if (threadId != null) {
//通过lua脚本进行延期
RFuture<Boolean> future = RedissonBaseLock.this.renewExpirationAsync(threadId);
future.onComplete((res, e) -> {
//如果e不为空,代表有异常,从待延期锁信息集合中删除当前锁,并退出
if (e != null) {
RedissonBaseLock.log.error("Can't update lock " + RedissonBaseLock.this.getRawName() + " expiration", e);
RedissonBaseLock.EXPIRATION_RENEWAL_MAP.remove(RedissonBaseLock.this.getEntryName());
} else {
//延期成功
//递归调度,进入下一次延期
if (res) {
RedissonBaseLock.this.renewExpiration();
}
}
});
}
}
}
}, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS);
ee.setTimeout(task);
}
}
最终是在Config类中被设置的
30000微秒,也就是30秒