一,Reids实现分布式锁原理
# 核心命令
setnx key value (set if not exist)
`只要key不存在,就保存key value;若存在,不操作。一个线程先拿到锁(setnx 保存key到value),其它线程再setnx key, 返回false。
对应代码:
redisTemplate.opsForValue().setIfAbsent(key, value)
保存成功返回true 失败返回false。注意:用完要释放锁。
使用注意:
1,线程拿到锁,业务逻辑抛异常,不能释放锁。
解决:把业务代码放进try catch finally。在finally释放锁。
2,线程拿到锁,突然服务挂了或网络问题,finally也不能执行,不能释放锁。
解决:设置key过期时间,set key时就传入过期时间。
3,高并发下,线程A进去业务逻辑 -> 线程A执行时间超过设置的过期时间 -> 这时释放了锁,线程B拿到锁,执行业务代码 -> 线程A此时执行完了,释放了B的锁,以此循环。
解决:线程A的key只能线程A删除。给每个线程一个UUID,作为 value 存入 redis,删除 key 前先校验 value。
代码:
public void methodA(){
String lockKey = "key";
String clientId = UUID.randomUUID().toString();
//设置锁,同时设置过期时间和唯一id。设置失败表示锁已被占用,返回错误信息。
Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS);
if(!result){
return "error";
}
try{
...业务逻辑...
}finally{
//当前锁只能由当前线程释放
if(clientId.equals(redisTemplate.opsForValue().get(lockKey))){
redisTemplate.delete(lockKey);
}
}
}
SpringBoot 整合 Redisson 工具更轻松帮我们解决以上的问题。
二,Redisson实现方式
1,添加依赖
<!--redisson客户端 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.12.3</version>
</dependency>
2,添加redisson配置文件
@Configuration
public class RedisConfiguration {
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.password}")
private String password;
@Bean
public Redisson redisson() {
Config config = new Config();
config.useSingleServer().setAddress("redis://" + redisHost + ":6379").setPassword(password).setDatabase(0);
return (Redisson) Redisson.create(config);
}
}
3,使用
@Autowired
private Redisson redisson;
public void methodA(){
RLock redissonLock = redisson.getLock(unitId.toString());
try {
redissonLock.lock(); //上锁 还可以用tryLock()、tryInterruptibly()。下面有它们的区别
...业务逻辑...
}finally {
redissonLock.unlock(); //释放锁
}
}
三,lock()、tryLock()、lockInterruptibly()区别
1,lock()
拿不到lock就不罢休,不然线程就一直block,比较无赖的做法。
2,tryLock()
马上返回,拿到lock就返回true,不然返回false。 带时间限制的tryLock(),拿不到lock,就等一段时间,超时返回false。
3,ryInterruptibly()
在锁上等待,直到获取锁,但是会响应中断,这个方法优先考虑响应中断,而不是响应锁的普通获取或重入获取。
评论区