一、实现方式一:redisTemplate
1. 引入redis启动器starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2. 配置yml
spring
redis:
host: 127.0.0.1
port: 6379
3. 代码实现
@Autowired
StringRedisTemplate redisTemplate;
/**
* 使用分布式锁来实现多个服务共享同一缓存中的数据
* (1)设置读写锁,失败则表明其他线程先于该线程获取到了锁,则执行自旋,成功则表明获取到了锁
* (2)获取锁成功,查询数据库,获取分类数据
* (3)释放锁
* @return
*/
public Map<String, Object> getRedisLock() {
String uuid= UUID.randomUUID().toString();
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
if(lock) {
// 加锁成功……执行业务
try{
// ……
} finally {
//确保一定会释放锁
String script="if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
// 删除锁
redisTemplate.execute(new DefaultRedisScript(script,Long.class),Arrays.asList("lock"),uuid);
}
} else {
// 加锁失败……休眠100ms 重试
// 自旋,调用自己
getRedisLock()
}
}
一、实现方式二:Redisson(官方推荐使用)
Redisson是架设在Redis基础上的一个Java驻内存数据网格(In-Memory Data Grid)
1. 引入redission依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.12.5</version>
</dependency>
2. 创建“MyRedisConfig” 配置类
@Configuration
public class MyRedisConfig {
@Bean(destroyMethod="shutdown")
public RedissonClient redisson() throws IOException {
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
return redisson;
}
}
3. 可重入锁(Reentrant Lock)
- 所有的锁都应该设置为可重入锁,来防止死锁。
- ps: 方法A调用方法B,两个方法都加的是1号锁,如果是可重入锁,A方法获取到锁后执行,执行到B方法后,B方法一看已经加了1号锁就直接执行B方法,执行完后,A方法释放锁。
- 如果是不可重入锁,A方法获取到1号锁后,调用B方法,B方法也需要获取到1号锁,造成死锁
3.1. 门狗原理
设想一种情况,一个请求线程在执行业务方法的时候,突然发生了中断,此时没有来得及执行释放锁操作,那么同时等待的另外一个线程是否会发生死锁?
文档原文引用:
大家都知道,如果负责储存这个分布式锁的Redisson节点宕机以后,而且这个锁正好处于锁住的状态时,这个锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改
wiki
@Autowired
RedissonClient redisson;
@GetMapping("/hello")
@ResponseBody
public String hello(){
//1.获取一把锁,只要名字一样,就是同一把锁
RLock lock = redisson.getLock("my-lock");
// 2.加锁
lock.lock(); // 阻塞式等待,直到能拿到锁,默认加锁时间30s
// 1)锁到自动续期,如果业务超长,运行期间自动给锁续上新到30s。不用担心业务时间过长后,锁自动过期被删掉
// 2)加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认在30s后自动删除,没有死锁问题。
try {
// 执行业务代码……
}finally {
// 3.解锁
lock.unlock();
}
return "hello";
}
3.2. 小结:redisson的lock特点
-
阻塞式等待。默认的锁的时间是30s。
-
锁定的制动续期,如果业务超长,运行期间会自动给锁续上新的30s,无需担心业务时间长,锁自动被删除的问题。
-
加锁的业务只要能够运行完成,就不会给当前锁续期,即使不手动解锁,锁默认在30s以后自动删除。
3.3. 扩展部分
- 关于续期周期,只要锁占领成功,就会自动启动一个定时任务【重新给锁设置过期时间,新的过期时间就是看门狗的默认时间】,每隔10s都会自动再次续期,续成30s。这个10s中是根据( internalLockLeasTime)/3得到的
4. Redisson-读写锁
- 保证一定能够读取到最新的数据,修改期间,写锁是一个排他锁(互斥锁),读锁是一个共享锁,写锁没释放读就必须等待。
@GetMapping("/write")
@ResponseBody
public String writeValue(){
RReadWriteLock writeLock=redisson.getReadWriteLock("rw-loc");
String uuid = null;
RLock lock = writeLock.writeLock();
lock.lock();
try {
log.info("写锁加锁成功");
uuid = UUID.randomUUID().toString();
redisTemplate.opsForValue().set("writeValue",uuid);
}finally {
lock.unlock();
log.info("写锁释放");
}
return uuid;
}
@GetMapping("/read")
@ResponseBody
public String redValue(){
String uuid = null;
RReadWriteLock readLock=redisson.getReadWriteLock("rw-loc");
RLock lock = readLock.readLock();
lock.lock();
try {
log.info("读锁加锁成功");
uuid = redisTemplate.opsForValue().get("writeValue");
}finally {
lock.unlock();
log.info("读锁释放");
}
return uuid;
}
}
4.1. 小结
读+读:相当于无锁,并发读,只会在redis中记录,所有当前的读锁,都会同时加锁成功
写+读:等待写锁释放;
写+写:阻塞方式
读+写:写锁等待读锁释放,才能加锁
- 所以只要存在写操作,不论前面是或后面执行的是读或写操作,都会阻塞。
网友评论