Redis的setnx在代码中是RedisTemplate.setIfAbsent(并不是.setnx)
注意为了防止在过程中异常导致无法释放锁,需要设置过期时间来解决,但是仍然存在问题,如果业务7秒处理,锁三秒释放,会出现释放不属于自己的锁的情况(锁是一个共用的,别人用着呢就被释放了)
使用UUID给锁加上唯一标识来解决误删锁的问题,但是仍然存在问题,没有原子性,其他线程可以进入打断当前线程
通过Redis的LUA脚本保持原子性,当前线程结束其他线程才能进入。
//UUID防止误删:redis中的setnx进行实现
@Override
public void testLock(){
//从redis中取出数据
String uuid= UUID.randomUUID().toString();
//1.获取当前锁
// Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent("lock", "lock");
//添加锁的过期时间,保证即使出现异常也可以实现解锁
Boolean ifAbsent = redisTemplate.opsForValue()
.setIfAbsent("lock", uuid,10, TimeUnit.SECONDS);
//2.如果获取到了锁,从redis中获取数据,数据+1 放回到redis中去
if(ifAbsent){
//获取锁成功,执行业务代码
//1.先从redis中通过key num获取值 key提前手动设置 num 初始值:0
String value = redisTemplate.opsForValue().get("num");
//2.如果值为空则非法直接返回即可
if (StringUtils.isBlank(value)) {
return;
}
//3.对num值进行自增加一
int num = Integer.parseInt(value);
redisTemplate.opsForValue().set("num", String.valueOf(++num));
//3.释放锁,LUA脚本实现
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
//设置LUA脚本
String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
"then\n" +
" return redis.call(\"del\",KEYS[1])\n" +
"else\n" +
" return 0\n" +
"end";
redisScript.setScriptText(script);
//设置返回结果
redisScript.setResultType(Long.class);
redisTemplate.execute(redisScript, Arrays.asList("lock"),uuid);
}else{
//没有获得到锁,重新获取
try {
//睡眠
Thread.sleep(100);
//自旋重试
this.testLock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Redis提供的分布式锁框架,可以将锁和业务逻辑分离开,方便编写业务。
(1) 编写配置类RedissonClient的配置类
@Data
@Configuration
@ConfigurationProperties(prefix = "spring.data.redis")//redis的配置信息在nacos中调用
public class RedissonConfig {
private String host;
private String password;
private String port;
private int timeout = 3000;
private static String ADDRESS_PREFIX = "redis://";
/**
* 自动装配
*
*/
@Bean
RedissonClient redissonSingle() {
Config config = new Config();
if(!StringUtils.hasText(host)){
throw new RuntimeException("host is empty");
}
SingleServerConfig serverConfig = config.useSingleServer()
.setAddress(ADDRESS_PREFIX + this.host + ":" + port)
.setTimeout(this.timeout);
if(StringUtils.hasText(this.password)) {
serverConfig.setPassword(this.password);
}
return Redisson.create(config);
}
}
(2)使用
//Redisson实现分布式锁
@Override
public void testLock(){
//1.首先通过Redisson创建锁对象
RLock lock = redissonClient.getLock("lock1");
//2.尝试获取锁
//(1) 阻塞一直等待直到获取锁,默认过期时间30s
lock.lock();
// //(2)获取到锁,锁过期时间10s
// lock.lock(10, TimeUnit.SECONDS);
// //(3)第一个参数:获取锁的时间 30s ;第二个参数:30s以内获取到了锁,过期时间为10s
// try {
// lock.tryLock(30,10,TimeUnit.SECONDS);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
//3.成功获取锁后编写业务代码
//获取锁成功,执行业务代码
//1.先从redis中通过key num获取值 key提前手动设置 num 初始值:0
String value = redisTemplate.opsForValue().get("num");
//2.如果值为空则非法直接返回即可
if (StringUtils.isBlank(value)) {
return;
}
//3.对num值进行自增加一
int num = Integer.parseInt(value);
redisTemplate.opsForValue().set("num", String.valueOf(++num));
//4.释放锁
lock.unlock();
}
推荐阅读: