第二部分:并发工具类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 @   SpecialSpeculator  阅读(48)  评论(0编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示
点击右上角即可分享
微信分享提示