基于 Redis 的 Redisson 分布式可重入读写锁 RReadWriteLock
,实现了 juc lock 包下的 ReadWriteLock
接口。
分布式可重入读写锁允许同时有多个读锁和一个写锁处于加锁状态。
-
ReadWriteLock
可以保证一定能读到最新数据,修改期间,写锁是一个排它锁(互斥锁、独享锁),读锁是一个共享锁; -
写锁没释放读锁必须等待;
-
读 + 读 :相当于无锁,并发读,只会在 Redis 中记录所有当前的读锁。他们都会同时加锁成功;
-
写 + 读 :必须等待写锁释放;
-
写 + 写 :阻塞;
-
读 + 写 :阻塞,有读锁,写也需要等待;
-
只要有写操作都必须等待;
@RestController
@RequestMapping(value = "/test")
public class TestController {
@Autowired
private RedissonClient redissonClient;
// 分布式读写锁
private static String rwLock = "rw-lock";
/**
* 读接口
*/
@GetMapping("/read")
public String readValue(@RequestParam(defaultValue = "0") int sleepTime){
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(rwLock);
RLock rLock = readWriteLock.readLock();
try {
// 读写锁
rLock.lock();
// 程序睡眠,模拟业务处理时间
TimeUnit.SECONDS.sleep(sleepTime);
} catch (Exception e) {
e.printStackTrace();
} finally {
rLock.unlock();
}
return String.valueOf(System.currentTimeMillis());
}
/**
* 写接口
*/
@GetMapping("/write")
public String writeValue(@RequestParam(defaultValue = "0") int sleepTime){
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(rwLock);
RLock rLock = readWriteLock.writeLock();
try {
// 加写锁
rLock.lock();
// 程序睡眠,模拟业务处理时间
TimeUnit.SECONDS.sleep(sleepTime);
} catch (Exception e) {
e.printStackTrace();
} finally {
rLock.unlock();
}
return String.valueOf(System.currentTimeMillis());
}
}
/**
* Redisson 配置类
*/
@Configuration
public class RedissonConfig {
@Value("${spring.redis.port}")
private String port;
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.client-name}")
private String clientName;
/**
* 所有对 Redisson 的使用都是通过 RedissonClient
*/
@Bean(destroyMethod = "shutdown")
public RedissonClient redisson() {
// 1、创建配置
Config config = new Config();
config.useSingleServer().setAddress("redis://" + host + ":" + port).setClientName(clientName).setPassword(password);
// 2、根据 Config 创建出 RedissonClient 实例
RedissonClient redisson = Redisson.create(config);
return redisson;
}
}
测试
- 读锁 + 读锁测试:先访问
localhost:8081/test/read?sleepTime=10
,再访问localhost:8081/test/read
不会阻塞; - 读锁 + 写锁测试:先访问
localhost:8081/test/read?sleepTime=10
,再访问localhost:8081/test/write
会阻塞; - 写锁 + 读锁测试:先访问
localhost:8081/test/write?sleepTime=10
,再访问localhost:8081/test/read
会阻塞; - 写锁 + 写锁测试:先访问
localhost:8081/test/write?sleepTime=10
,再访问localhost:8081/test/write
会阻塞;
看门狗避免死锁问题
如果负责储存这个分布式锁的 Redis 节点宕机以后,而且这个锁正好处于锁住的状态时,这样会产生死锁问题。为了避免这种情况的发生,Redisson 内部提供了一个监控锁的看门狗,每加一个锁默认都会设置一个 30s 的默认时间,在 Redisson 实例被关闭前,会不断的延长锁的有效期,这样可以有效避免死锁问题。