第二部分:并发工具类17->ReadWriteLock:如何快速实现一个完备的缓存

1.其他工具类

用途:分场景优化性能,提升易用性

2.并发场景,读多写少

使用缓存,缓存元数据,缓存基础数据

缓存的数据一定是读多写少

3.读写锁ReadWriteLock

非常容易使用,性能很好

1.允许多个线程同时读共享变量
2.只允许一个线程写共享变量
3.如果一个写线程正在执行写操作,此时禁止读线程读共享变量

读写锁与互斥锁ReentrantLock重要区别就是读写锁允许多个线程同时读共享变量,互斥锁不允许
读写锁在读多写少场景下性能优于互斥锁的关键
读写锁的写操作是互斥的,当一个线程在写共享变量时,不允许其他线程执行写操作和读操作

4.使用读写锁ReadWriteLock,用在缓存上

Cache<K,V> 类,参数K代表缓存里key的类型,V代表缓存value的类型,都是泛型
缓存数据保存在Cache类的内部hashmap中,hashmap不是线程安全,使用读写锁ReadWriteLock保证线程安全

ReadWriteLock是接口,实现类ReentrantReadWriteLock,
缓存类的方法get和pu都用到了读写锁


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) {
    r.lock();
    try { return m.get(key); }
    finally { r.unlock(); }
  }
  // 写缓存
  V put(K key, V value) {
    w.lock();
    try { return m.put(key, v); }
    finally { w.unlock(); }
  }
}

5.缓存的扩展知识

1.解决缓存数据初始化问题,可以采用一次性加载,也可以使用按需加载
2.源头数据不大,可以一次性加载方式,这种方式最简单,应用启动时把源头数据查出来,依次调用put方法就可以

3.源头数据大,按需加载,也成为懒加载,当应用查询缓存,数据不在缓存里,才触发加载源头相关数据进缓存

6.缓存按需加载

数据源头是数据库,如果缓存中没有缓存目标对象,就需要从数据库中加载,然后写入缓存,写缓存需要用到写锁,w.lock()
在获取写锁后,没有直接查库,而是重新验证了一次缓存中是否存在,不存在才会去查库?
高并发下,多线程竞争写锁,缓存是空的,线程T1获取写锁后,直接查询并更新缓存,释放锁,线程T2会再次获取到锁,如果不验证,会再次查库
所以多加一部验证,能避免高并发场景下重复查询数据的问题


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; 
  }
}

7.锁的升级

先获取读锁,然后再获取写锁;这种是不允许的,ReadWriteLock

8.总结

读写锁类似ReentrantLock,也支持公平锁和非公平锁

只有写锁支持条件变量,读锁不支持条件变量
newCondition()

双写方案,写缓存+写数据库
超时机制,缓存不是长久有效,缓存的数据超过时效,缓存中就实效了。

posted @ 2021-07-06 14:37  SpecialSpeculator  阅读(45)  评论(0编辑  收藏  举报