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()

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

    原创:做时间的朋友
  • 相关阅读:
    Android 开发 深入理解Handler、Looper、Messagequeue 转载
    Android 开发 Handler的基本使用
    Java 学习 注解
    Android 开发 AlarmManager 定时器
    Android 开发 框架系列 百度语音合成
    Android 开发 框架系列 Google的ORM框架 Room
    Android 开发 VectorDrawable 矢量图 (三)矢量图动画
    Android 开发 VectorDrawable 矢量图 (二)了解矢量图属性与绘制
    Android 开发 VectorDrawable 矢量图 (一)了解Android矢量图与获取矢量图
    Android 开发 知晓各种id信息 获取线程ID、activityID、内核ID
  • 原文地址:https://www.cnblogs.com/PythonOrg/p/14976861.html
Copyright © 2011-2022 走看看