ReadWriteLock
概念
维护一对关联锁,一个只用于读操作,一个只用于写操作;
读锁可以由多个读线程同时持有,写锁是排他的。同一时间,两把锁不能被不同线程持有。就类似如图的效果:
同一时间只能持有读锁或者写锁。
缓存
在进入正题前先来了解一下缓存的概念,先用伪代码模拟一下Redis缓存的使用场景:
//实现缓存
//使用数据
class TeacherInfoCache{
//数据是否可用
static volatile boolean cacheValid;
//锁
static final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
//查询并使用数据
static void processCacheData(String dataKey){
Object data = null;
rwl.readLock().lock();
try{
//判断数据是否可用
if(cacheValid){
data = Redis.data.get(dataKey);
}else {
data=DataBase.queryUserInfo();
Redis.data.put(dataKey,data);
cacheValid = true;
}
}finally {
rwl.readLock().unlock();
}
}
}
class DataBase{
static String queryUserInfo(){
System.out.println("查询数据库。。。");
return "name:Kody,age:40,gender:true";
}
}
class Redis{
static Map<String,Object> data = new HashMap<>();
}
可以看到缓存的使用主要就是把从数据库查询出的数据根据key存放在redis中。在查询时,会优先取差缓存中是否有该数据,如果没有则去查数据库并再次存入。
缓存雪崩
缓存雪崩就容易出现在高并发的情况下有很多线程去查缓存都查不到,然后只能去数据库查,导致数据库压力太大而崩溃的现象,就像上面这段代码,其实很容易就会造成缓存雪崩的现象,当高并发情况下就会并发访问数据库,这时候就把代码优化一下:
//实现缓存
//使用数据
class TeacherInfoCache{
//数据是否可用
static volatile boolean cacheValid;
//锁
static final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
//查询并使用数据
static void processCacheData(String dataKey){
Object data = null;
rwl.readLock().lock();
try{
//判断数据是否可用
if(cacheValid){
data = Redis.data.get(dataKey);
}else {
rwl.readLock().unlock();
rwl.writeLock().lock();
try{
data=DataBase.queryUserInfo();
Redis.data.put(dataKey,data);
cacheValid = true;
}finally{
rwl.readLock().lock();//必须在解写锁之前再加读锁,否则就会在锁完全释放的真空期间有可能失去锁的占用权而造成数据不一致的危险。
rwl.writeLock().unlock();
}
}
}finally {
rwl.readLock().unlock();
}
}
}
class DataBase{
static String queryUserInfo(){
System.out.println("查询数据库。。。");
return "name:Kody,age:40,gender:true";
}
}
class Redis{
static Map<String,Object> data = new HashMap<>();
}
上述代码中肯定有人会有疑问,为什么加了写锁之后还能加读锁,读锁和写锁不是互斥的么。这其实是一种特殊情况,也就是锁降级的情况,在加读锁之后立马释放了写锁,完成了从写锁到读锁的降级。
AQS
AQS本质上是一个模版设计模式,本质上就是抽取不同类相同的功能,提取成一个模版,实现了复用。就如PPT,就是典型的模板,可以自定义模板的标题,页码等,可以把它们抽取成一个模板,具体不同的实现再由个人去实现。如果我们发现我们的两个锁类有50%的地方是相似的,那么就用模板设计模式就可以实现了。AQS的作用就是如此。
AQS的本质
其实大多数方法都是抽离于其它的锁,而一些不够具体的方法是留白的,即需要用户自己去重写,否则就抛出异常。