原理
流程分析:
- 尝试获取锁,返回 null 则说明加锁成功,返回一个数值,则说明已经存在该锁,ttl 为锁的剩余存活时间。
- 如果此时客户端 2 进程获取锁失败,那么使用客户端 2 的线程 id(其实本质上就是进程 id)通过 Redis 的 channel
订阅锁释放的事件。如果等待的过程中一直未等到锁的释放事件通知,当超过最大等待时间则获取锁失败,返回 false。如果等到了锁的释放事件的通知,则开始进入一个不断重试获取锁的循环。 - 循环中每次都先试着获取锁,并得到已存在的锁的剩余存活时间。如果在重试中拿到了锁,则直接返回。如果锁当前还是被占用的,那么等待释放锁的消息,具体实现使用了JDK 的信号量 Semaphore 来阻塞线程,当锁释放并发布释放锁的消息后,信号量的 release()方法会被调用,此时被信号量阻塞的等待队列中的一个线程就可以继续尝试获取锁了。
特别注意:以上过程存在一个细节,这里有必要说明一下,也是分布式锁的一个关键点:当锁正在被占用时,等待获取锁的进程并不是通过一个 while(true) 死循环去获取锁,而是利用了 Redis 的发布订阅机制,通过 await 方法阻塞等待锁的进程,有效的解决了无效的锁申请浪费资源的问题。
代码
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.16.4</version>
</dependency>
@Configuration
public class RedissonConfig {
@Value("${redisson.address}")
private String address;//redis://xxx.x.x.x:6379
@Value("${redisson.password}")
private String password;
//单例模式
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer()
.setAddress(address)
.setPassword(password);
return Redisson.create(config);
}
}
//1.获取锁
RLock lock = redissonClient.getLock("xxxx");
// 2.加锁
lock.lock();
try {
//业务代码
} finally {
//3.解锁
lock.unlock();
}
注意
加锁解锁尽量放到方法头尾,防止遗漏校验.