概述:
Redisson是一个在Redis的基础上实现的Java驻内存数据网格,它不仅提供了一系列的分布式的java常用对象,还提供了许多分布式服务,包括Map,List,Set,Lock.
测试使用:
<!-- Redisson分布式锁-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.12.0</version>
</dependency>
@Configuration
public class MyRedissonConfig {
/** 所有对Redisson的使用都是通过RedissonClient对象 */
// 注意:此处讲到@@Autowired之后注入的名字最好和redisson()方法的名字相同
@Bean(destroyMethod = "shutdown")
public RedissonClient redisson() throws IOException{
// 1.创建配置
Config config = new Config();
// 2.使用单节点模式
config.useSingleServer().setAddress("redis://192.168.56.10:6379");
// 3.根据config创建出RedissonClient实例
RedissonClient redissonClient = Redisson.create(config);
return redissonClient;
}
}
@Autowired
RedissonClient redissonClient;
@Test
public void redisson() {
System.out.println(redissonClient);
}
1.可重入锁 lock锁
A{
...
B{}
...
}
可重入锁:
例如对A加了1号锁,B同时也想加1号锁,可重入锁就是,B看到A已经加了1好锁,B就直接拿来用,B可以直接执行,执行完成A释放锁
不可重入锁:
A持有1号锁,不可重入锁,B要等待A来释放1号锁,才能持有1号锁,这就形成了一个死锁,因为B永远等不到A释放1号锁,因为A还想等B执行完成才释放锁
所有的锁都应该设计为可重入锁,来避免死锁问题
@ResponseBody
@GetMapping("/hello")
public String hellp(){
// 获取一把锁,只要锁的名字一样,就是同一把锁
RLock lock = redissonClient.getLock("my-lock");
// 加锁 这个是阻塞式等待
lock.lock();
try {
System.out.println("加锁成功,执行业务..." + Thread.currentThread().getId());
// 模拟业务执行30s
Thread.sleep(30000);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 解锁
System.out.println("释放锁..." + Thread.currentThread().getId());
lock.unlock();
}
return "hello";
}
结果:
加锁成功,执行业务...116
释放锁...116
加锁成功,执行业务...117
释放锁...117
假设finall中的解锁代码没有运行,redisson会不会出现死锁呢?
-
锁的自动续期:如果业务超长,运行期间自动给锁续上新的30s(redisson默认加的锁都是30s),不用担心业务时间长,锁自动过期被删掉
-
加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁也会默认在30s后自动删除
2.Redisson - 读写锁
读锁跟写锁成对存在,只要写锁存在,读锁就得等待; 并发读之间互不影响;但是只能有一个去写
/** 修改期间,写锁是一个排他锁(互斥锁),读锁是一个共享锁 写锁没释放读必须等待*/
@ResponseBody
@GetMapping("/write")
public String write(){
RReadWriteLock lock = redissonClient.getReadWriteLock("rw-lock");
String s = "";
RLock writeLock = lock.writeLock();
try {
// 改数据的时候加写锁,读数据加读锁
writeLock.lock();
s = UUID.randomUUID().toString();
// 模拟业务执行,要写30s
Thread.sleep(30000);
redisTemplate.opsForValue().set("writeValue",s);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
writeLock.unlock();
}
return s;
}
@ResponseBody
@GetMapping("/read")
public String read() {
RReadWriteLock lock = redissonClient.getReadWriteLock("rw-lock");
RLock readLock = lock.readLock();
String s = "";
// 加读锁
readLock.lock();
try {
s = redisTemplate.opsForValue().get("writeValue");
} catch (Exception e) {
e.printStackTrace();
} finally {
readLock.unlock();
}
return s;
}
3.Redisson - 信号量
/** 信号量测试 */
@ResponseBody
@GetMapping("/park")
public String park() throws InterruptedException {
RSemaphore park = redissonClient.getSemaphore("park");
park.acquire();
// tryAcquire会得到一个结果true或者false,不用继续等待
// boolean b = park.tryAcquire();
// 可用作限流
return "ok";
}
@ResponseBody
@GetMapping("/gogo")
public String gogo() throws InterruptedException {
RSemaphore park = redissonClient.getSemaphore("park");
park.release();
return "ok";
}
4.Redisson - 闭锁
/** 闭锁 */
@ResponseBody
@GetMapping("/lockdoor")
public String lockdoor() throws InterruptedException {
RCountDownLatch door = redissonClient.getCountDownLatch("door");
door.trySetCount(5);
door.await(); // 等待闭锁完成
return "放假了 ...";
}
@ResponseBody
@GetMapping("/gogogo/{id}")
public String gogogo(@PathVariable("id") Long id) throws InterruptedException {
RCountDownLatch door = redissonClient.getCountDownLatch("door");
door.countDown(); // 计数减一
return id + "班的人都走了 ...";
}
5.Redisson - 缓存一致性解决
当修改数据库时, 缓存中的数据如何与数据库中保持一致
1 双写模式
2 失效模式
缓存数据一致性的解决:
1.缓存数据+过期时间,足够解决大部分业务对于缓存的要求
2.通过加锁保证并发读写,写写的时候按照顺序排好队,读读无所谓,所以适合使用读写锁