一.简介

读多写少场景,实际工作中,为了优化性能,我们经常会使用缓存,例如缓存元数据、缓存基础数据等,这就是一种典型的读多写少应用场景,缓存之所以能提升性能,一个重要的条件就是缓存的数据一定是读多写少的,例如元数据和基础数据基本上不会发生变化(写少),使用它们的地方却很多(读多)。

针对这种场景,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并发编程实战》

公众号

Java并发ReadWriteLock_多线程

微信公众号(bigdata_limeng)