在很多场景中,对于一个对象,经常读多写少,为了优化性能,使用了缓存机制,缓存之所以如此重要,就是因为缓存的数据就是读多写少的,
并且针对缓存,我们在java中专门制定了一种并发的锁,读写锁(ReadWriteLock)
读写锁,顾名思义,就是遵循了允许多个线程同时读取共享变量,但只有一个线程可以对变量进行写操作 在写的时候,不允许读
读写锁最大的进步就是读写锁允许多个线程同时读取变量,但是写的操作是互斥的
在Java中,关于ReadWirteLock来说是一个接口,实现类是ReentrantReadWriteLock,支持可重入的读写锁
接下来这段代码,在结合了读写锁的基础上使用了懒加载机制,如果缓存中没有缓存目标对象,从数据库中加载并写入,写入用到了写锁
class Cache<K,V> {
final Map<K, V> m =
new HashMap<>();
final ReadWriteLock rwl =
new ReentrantReadWriteLock();
final Lock r = rwl.readLock();
final Lock w = rwl.writeLock();
V get(K key) {
V v = null;
//读缓存
r.lock(); ①
try {
v = m.get(key); ②
} finally{
r.unlock(); ③
}
//缓存中存在,返回
if(v != null) { ④
return v;
}
//缓存中不存在,查询数据库
w.lock(); ⑤
try {
//再次验证
//其他线程可能已经查询过数据库
v = m.get(key); ⑥
if(v == null){ ⑦
//查询数据库
//v=省略查询代码
m.put(key, v);
}
} finally{
w.unlock();
}
return v;
}
}
在这段代码中,在获取到写锁的时候,并没有直接去查询数据库,而是又一次的查询了缓存
这是为了避免在使用到锁的时候,前几个获取到的线程已经加载进入缓存了
避免了过多的IO操作
读写锁的一个特有的个性就是支持锁的降级
首先说有一种应用场景,就是先进行读取,读取完成后发现没有,在进行查询写入
换成读写锁的场景就是,先获取到了读锁,在升级为写锁
在读写锁中,对于升级是不支持的,上述的流程会发生错误
但是支持锁的降级,可以在获取到写锁的同时拿到读锁
class CachedData {
Object data;
volatile boolean cacheValid;
final ReadWriteLock rwl =
new ReentrantReadWriteLock();
// 读锁
final Lock r = rwl.readLock();
//写锁
final Lock w = rwl.writeLock();
void processCachedData() {
// 获取读锁
r.lock();
if (!cacheValid) {
// 释放读锁,因为不允许读锁的升级
r.unlock();
// 获取写锁
w.lock();
try {
// 再次检查状态
if (!cacheValid) {
data = …
cacheValid = true;
}
// 释放写锁前,降级为读锁
// 降级是可以的
r.lock(); ①
} finally {
// 释放写锁
w.unlock();
}
}
// 此处仍然持有读锁
try {use(data);}
finally {r.unlock();}
}
}
在这里就是支持在获取到写锁的时候获取读锁
读写锁,在Java中的实现,是和可重入锁一致的,在构造函数中,支持公平模式和非公平模式
但是,对于读写锁,只有写锁支持了条件变量,读锁是不支持条件变量的,读锁调用 newCondition会抛出UnsupportedOperationException异常