zoukankan      html  css  js  c++  java
  • 第二部分:并发工具类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()

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

    原创:做时间的朋友
  • 相关阅读:
    P2464 [SDOI2008]郁闷的小J
    P2157 [SDOI2009]学校食堂
    P3201 [HNOI2009]梦幻布丁
    P2051 [AHOI2009]中国象棋
    UVA11987 Almost Union-Find
    P2577 [ZJOI2005]午餐
    洛谷 3768简单的数学题(莫比乌斯反演+杜教筛)
    LOJ#6229. 这是一道简单的数学题(莫比乌斯反演+杜教筛)
    BZOJ 4555:[TJOI2016&HEOI2016]求和(第二类斯特林数+NTT)
    BZOJ 4816[SDOI2017]数字表格(莫比乌斯反演)
  • 原文地址:https://www.cnblogs.com/PythonOrg/p/14976861.html
Copyright © 2011-2022 走看看