ReadWriteLock
读多写少场景;缓存;锁升级;
读写锁遵循的规则
- 允许多个线程同时读共享变量;
- 只允许一个线程写共享变量;
- 如果一个写线程正在执行写操作,此时禁止读线程读共享变量。
具体实现类:ReentrantReadWriteLock
从这个实现类命名便可得知其支持可重入,其也实现了Lock接口,读锁之间共享资源,写锁之间互斥;支持锁降级不支持锁升级;读锁为释放不能申请写锁,写锁未释放可以申请读锁;
读写锁类似于 ReentrantLock,也支持公平模式和非公平模式。读锁和写锁都实现了java.util.concurrent.locks.Lock 接口,所以除了支持 lock() 方法外,tryLock()、lockInterruptibly() 等方法也都是支持的。但是有一点需要注意,那就是只有写锁支持条件变量,读锁是不支持条件变量的,读锁调用 newCondition() 会抛出UnsupportedOperationException 异常。
锁降级的意义?
提高一边写一边读的效率,部分更新完成后就降级为读锁;防止写锁的一直占用阻塞大量读锁;
官方示例:
public void test(){
boolean cacheValid = false;
r.lock();
if (!cacheValid){
// 释放读锁,因为不允许读锁的升级
r.unlock();
// 获取写锁
w.lock();
try {
//写缓存操作
cacheValid = true;
// 释放写锁前降级为读锁
r.lock();
}finally {
w.unlock();
}
}
try {
// 此处仍然持有读锁 对读取的数据进行操作
}finally {
r.unlock();
}
}
利用特性实现缓存:
/**
* @author zhan
* @version 2021/7/1
*/
public class CustomerCache<K,V> {
private final ReentrantReadWriteLock rlk = new ReentrantReadWriteLock();
private final Lock r = rlk.readLock();
private final Lock w = rlk.writeLock();
private final HashMap<K, V> map = new HashMap<>();
public CustomerCache(){
}
public V get(K k){
r.lock();
try {
return map.get(k);
}finally {
r.unlock();
}
}
public void set(K k, V v){
w.lock();
try {
map.put(k, v);
}finally {
w.unlock();
}
}
public V getIfNullPut(K k){
V v;
r.lock();
try {
v = map.get(k);
}finally {
r.unlock();
}
if (v != null){
return v;
}
w.lock();
try {
// 二次校验,可能被别的线程添加了缓存
v = map.get(k);
if (v == null){
v = selectDb();
return map.put(k, v);
}
}finally {
w.unlock();
}
return v;
}
public V selectDb(){
return null;
}
}