一.简介
读多写少场景,实际工作中,为了优化性能,我们经常会使用缓存,例如缓存元数据、缓存基础数据等,这就是一种典型的读多写少应用场景,缓存之所以能提升性能,一个重要的条件就是缓存的数据一定是读多写少的,例如元数据和基础数据基本上不会发生变化(写少),使用它们的地方却很多(读多)。
针对这种场景,Java SDK并发包提供了读写锁——ReadWriteLock,非常容易使用,并且性能很好。
二.读写锁
- 允许多个线程同时读共享变量;
- 只允许一个线程写共享变量;
- 如果一个写线程正在执行写操作,此时禁止读线程读共享变量。
读写锁与互斥锁的一个重要区别,读写锁允许多个线程同时读共享变量,而互斥锁不允许。这是读写锁在读多写少的场景性能优于互斥锁的关键。
三.示例-缓存
声明CacheTest对象,HashMap是线程不安全,使用ReadWriteLock来保证线程安全,ReadWriteLock是一个接口,它的实现类是ReentrantReadWriteLock,支持可重入。
public class CacheTest2<K,V> {
final Map<K,V> map = new HashMap<>();
final ReadWriteLock rwl = new ReentrantReadWriteLock();
final Lock r = rwl.readLock();
final Lock w = rwl.writeLock();
final Condition needPush = w.newCondition();
public V get(K key){
r.lock();
try {
while (map.isEmpty()) {
try {
needPush.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
V v = map.get(key);
map.remove(key);
return v;
}finally {
r.unlock();
}
}
public V put(K key,V value){
w.lock();
try {
V v = map.put(key, value);
needPush.signalAll();
return v;
}finally {
w.unlock();
}
}
public V getByAdd(K key){
r.lock();
V value = null;
try {
value = map.get(key);
}finally {
r.unlock();
}
if(value != null) return value;
w.lock();
try {
value = map.get(key);
if(value == null){
//value = "查询到数据"
map.put(key ,value);
}
}finally {
w.unlock();
}
return value;
}
public static void main(String[] args) {
CacheTest2<String, String> cache = new CacheTest2<>();
cache.put("key","value");
System.out.println(cache.get("key"));
}
}
读写锁类似于 ReentrantLock,也支持公平模式和非公平模式。读锁和写锁都实现了 java.util.concurrent.locks.Lock 接口,所以除了支持 lock() 方法外,tryLock()、lockInterruptibly() 等方法也都是支持的。但是有一点需要注意,那就是只有写锁支持条件变量,读锁是不支持条件变量的,读锁调用 newCondition() 会抛出 UnsupportedOperationException 异常。
参考
《Java并发编程实战》
公众号
微信公众号(bigdata_limeng)