热门标签 | HotTags
当前位置:  开发笔记 > 程序员 > 正文

JDK动态代理之WeakCache缓存的实现机制

这篇文章主要介绍了JDK动态代理之WeakCache缓存的实现机制

上一篇我们分析了Proxy类的内部是怎样产生代理类的,我们看到了Proxy内部用到了缓存机制,如果根据提供的类加载器和接口数组能在缓存中找到代理类就直接返回该代理类,否则会调用ProxyClassFactory工厂去生成代理类。这里用到的缓存是二级缓存,它的一级缓存key是根据类加载器生成的,二级缓存key是根据接口数组生成的。具体的内部机制我们直接贴上代码详细解释。

//Reference引用队列
private final ReferenceQueue refQueue = new ReferenceQueue<>();
//缓存的底层实现, key为一级缓存, value为二级缓存。 为了支持null, map的key类型设置为Object
private final ConcurrentMap>> 
                            map = new ConcurrentHashMap<>();
//reverseMap记录了所有代理类生成器是否可用, 这是为了实现缓存的过期机制
private final ConcurrentMap, Boolean> reverseMap = new ConcurrentHashMap<>();
//生成二级缓存key的工厂, 这里传入的是KeyFactory
private final BiFunction subKeyFactory;
//生成二级缓存value的工厂, 这里传入的是ProxyClassFactory
private final BiFunction valueFactory;

//构造器, 传入生成二级缓存key的工厂和生成二级缓存value的工厂
public WeakCache(BiFunction subKeyFactory, BiFunction valueFactory) {
  this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
  this.valueFactory = Objects.requireNonNull(valueFactory);
}

首先我们看一下WeakCache的成员变量和构造器,WeakCache缓存的内部实现是通过ConcurrentMap来完成的,成员变量map就是二级缓存的底层实现,reverseMap是为了实现缓存的过期机制,subKeyFactory是二级缓存key的生成工厂,通过构造器传入,这里传入的值是Proxy类的KeyFactory,valueFactory是二级缓存value的生成工厂,通过构造器传入,这里传入的是Proxy类的ProxyClassFactory。接下来我们看一下WeakCache的get方法。

public V get(K key, P parameter) {
  //这里要求实现的接口不能为空
  Objects.requireNonNull(parameter);
  //清除过期的缓存
  expungeStaleEntries();
  //将ClassLoader包装成CacheKey, 作为一级缓存的key
  Object cacheKey = CacheKey.valueOf(key, refQueue);
  //获取得到二级缓存
  ConcurrentMap> valuesMap = map.get(cacheKey);
  //如果根据ClassLoader没有获取到对应的值
  if (valuesMap == null) {
    //以CAS方式放入, 如果不存在则放入,否则返回原先的值
    ConcurrentMap> oldValuesMap = map.putIfAbsent(cacheKey, 
        valuesMap = new ConcurrentHashMap<>());
    //如果oldValuesMap有值, 说明放入失败
    if (oldValuesMap != null) {
      valuesMap = oldValuesMap;
    }
  }
  //根据代理类实现的接口数组来生成二级缓存key, 分为key0, key1, key2, keyx
  Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
  //这里通过subKey获取到二级缓存的值
  Supplier supplier = valuesMap.get(subKey);
  Factory factory = null;
  //这个循环提供了轮询机制, 如果条件为假就继续重试直到条件为真为止
  while (true) {
    //如果通过subKey取出来的值不为空
    if (supplier != null) {
      //在这里supplier可能是一个Factory也可能会是一个CacheValue
      //在这里不作判断, 而是在Supplier实现类的get方法里面进行验证
      V value = supplier.get();
      if (value != null) {
        return value;
      }
    }
    if (factory == null) {
      //新建一个Factory实例作为subKey对应的值
      factory = new Factory(key, parameter, subKey, valuesMap);
    }
    if (supplier == null) {
      //到这里表明subKey没有对应的值, 就将factory作为subKey的值放入
      supplier = valuesMap.putIfAbsent(subKey, factory);
      if (supplier == null) {
        //到这里表明成功将factory放入缓存
        supplier = factory;
      }
      //否则, 可能期间有其他线程修改了值, 那么就不再继续给subKey赋值, 而是取出来直接用
    } else {
      //期间可能其他线程修改了值, 那么就将原先的值替换
      if (valuesMap.replace(subKey, supplier, factory)) {
        //成功将factory替换成新的值
        supplier = factory;
      } else {
        //替换失败, 继续使用原先的值
        supplier = valuesMap.get(subKey);
      }
    }
  }
}

WeakCache的get方法并没有用锁进行同步,那它是怎样实现线程安全的呢?因为它的所有会进行修改的成员变量都使用了ConcurrentMap,这个类是线程安全的。因此它将自身的线程安全委托给了ConcurrentMap, get方法尽可能的将同步代码块缩小,这样可以有效提高WeakCache的性能。我们看到ClassLoader作为了一级缓存的key,这样可以首先根据ClassLoader筛选一遍,因为不同ClassLoader加载的类是不同的。然后它用接口数组来生成二级缓存的key,这里它进行了一些优化,因为大部分类都是实现了一个或两个接口,所以二级缓存key分为key0,key1,key2,keyX。key0到key2分别表示实现了0到2个接口,keyX表示实现了3个或以上的接口,事实上大部分都只会用到key1和key2。这些key的生成工厂是在Proxy类中,通过WeakCache的构造器将key工厂传入。这里的二级缓存的值是一个Factory实例,最终代理类的值是通过Factory这个工厂来获得的。

private final class Factory implements Supplier {
  //一级缓存key, 根据ClassLoader生成
  private final K key;
  //代理类实现的接口数组
  private final P parameter;
  //二级缓存key, 根据接口数组生成
  private final Object subKey;
  //二级缓存
  private final ConcurrentMap> valuesMap;

  Factory(K key, P parameter, Object subKey,
      ConcurrentMap> valuesMap) {
    this.key = key;
    this.parameter = parameter;
    this.subKey = subKey;
    this.valuesMap = valuesMap;
  }

  @Override
  public synchronized V get() {
    //这里再一次去二级缓存里面获取Supplier, 用来验证是否是Factory本身
    Supplier supplier = valuesMap.get(subKey);
    if (supplier != this) {
      //在这里验证supplier是否是Factory实例本身, 如果不则返回null让调用者继续轮询重试
      //期间supplier可能替换成了CacheValue, 或者由于生成代理类失败被从二级缓存中移除了
      return null;
    }
    V value = null;
    try {
      //委托valueFactory去生成代理类, 这里会通过传入的ProxyClassFactory去生成代理类
      value = Objects.requireNonNull(valueFactory.apply(key, parameter));
    } finally {
      //如果生成代理类失败, 就将这个二级缓存删除
      if (value == null) {
        valuesMap.remove(subKey, this);
      }
    }
    //只有value的值不为空才能到达这里
    assert value != null;
    //使用弱引用包装生成的代理类
    CacheValue cacheValue = new CacheValue<>(value);
    //将包装后的cacheValue放入二级缓存中, 这个操作必须成功, 否则就报错
    if (valuesMap.replace(subKey, this, cacheValue)) {
      //将cacheValue成功放入二级缓存后, 再对它进行标记
      reverseMap.put(cacheValue, Boolean.TRUE);
    } else {
      throw new AssertionError("Should not reach here");
    }
    //最后返回没有被弱引用包装的代理类
    return value;
  }
}

我们再看看Factory这个内部工厂类,可以看到它的get方法是使用synchronized关键字进行了同步。进行get方法后首先会去验证subKey对应的suppiler是否是工厂本身,如果不是就返回null,而WeakCache的get方法会继续进行重试。如果确实是工厂本身,那么就会委托ProxyClassFactory生成代理类,ProxyClassFactory是在构造WeakCache的时候传入的。所以这里解释了为什么最后会调用到Proxy的ProxyClassFactory这个内部工厂来生成代理类。生成代理类后使用弱引用进行包装并放入reverseMap中,最后会返回原装的代理类。

至此已经为大家详细揭示了WeakCache缓存的实现包括它的一级缓存和二级缓存实现的原理,以及二级缓存key生成的原理,还有最后它是怎样调用ProxyClassFactory来生成代理类的。在下一篇中将会深入ProxyGenerator这个类,来看看具体的代理类的字节码生成过程。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


推荐阅读
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • 本文介绍了新款奇骏的两个让人上瘾的功能,分别是智能互联系统和BOSE音响。通过对新款奇骏的配置和功能进行评测,探讨了这两个新增功能的使用体验和优势。此外,还介绍了新款奇骏的其他配置和改进,如增加的座椅和驾驶辅助系统,以及内饰的舒适性提升。对于喜欢音响的消费者来说,BOSE音响的升级也是一个亮点。最后,文章提到了BOSE音响的数字还原能力,以及7座版无法配备BOSE音响的原因。 ... [详细]
  • 本文详细介绍了相机防抖的设置方法和使用技巧,包括索尼防抖设置、VR和Stabilizer档位的选择、机身菜单设置等。同时解释了相机防抖的原理,包括电子防抖和光学防抖的区别,以及它们对画质细节的影响。此外,还提到了一些运动相机的防抖方法,如大疆的Osmo Action的Rock Steady技术。通过本文,你将更好地理解相机防抖的重要性和使用技巧,提高拍摄体验。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 本文详细介绍了华为4GLTE路由器B310的外置天线安装和设置方法。通过连接电源和网线,输入路由器的IP并登陆设置页面,选择手动设置和手动因特网设置,输入ISP提供商的用户名和密码,并设置MTU值。同时,还介绍了无线加密的设置方法。最后,将外网线连在路由器的WAN口即可使用。 ... [详细]
  • 本文讨论了前端工程化的准备工作,主要包括性能优化、安全防护和监控等方面需要注意的事项。通过系统的答案,帮助前端开发者更好地进行工程化的准备工作,提升网站的性能、安全性和监控能力。 ... [详细]
  • Java String与StringBuffer的区别及其应用场景
    本文主要介绍了Java中String和StringBuffer的区别,String是不可变的,而StringBuffer是可变的。StringBuffer在进行字符串处理时不生成新的对象,内存使用上要优于String类。因此,在需要频繁对字符串进行修改的情况下,使用StringBuffer更加适合。同时,文章还介绍了String和StringBuffer的应用场景。 ... [详细]
  • MyBatis错题分析解析及注意事项
    本文对MyBatis的错题进行了分析和解析,同时介绍了使用MyBatis时需要注意的一些事项,如resultMap的使用、SqlSession和SqlSessionFactory的获取方式、动态SQL中的else元素和when元素的使用、resource属性和url属性的配置方式、typeAliases的使用方法等。同时还指出了在属性名与查询字段名不一致时需要使用resultMap进行结果映射,而不能使用resultType。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 如何修改路由器密码?路由器登录密码和无线密码的修改方法
    本文介绍了修改路由器密码的两种方法:一是修改路由器登录口令,需要进入路由器后台进行操作;二是修改无线连接密码,通过进入路由器后台的无线设置和无线安全设置进行修改。详细步骤包括复位处理、登录路由器后台、选择系统工具、填入用户名和用户密码、保存修改等。 ... [详细]
  • 本文介绍了2019年上半年内蒙古计算机软考考试的报名通知和考试时间。考试报名时间为3月1日至3月23日,考试时间为2019年5月25日。考试分为高级、中级和初级三个级别,涵盖了多个专业资格。报名采取网上报名和网上缴费的方式进行,报考人员可登录内蒙古人事考试信息网进行报名。详细内容请点击查看。 ... [详细]
  • Tomcat/Jetty为何选择扩展线程池而不是使用JDK原生线程池?
    本文探讨了Tomcat和Jetty选择扩展线程池而不是使用JDK原生线程池的原因。通过比较IO密集型任务和CPU密集型任务的特点,解释了为何Tomcat和Jetty需要扩展线程池来提高并发度和任务处理速度。同时,介绍了JDK原生线程池的工作流程。 ... [详细]
  • 本文介绍了作者在开发过程中遇到的问题,即播放框架内容安全策略设置不起作用的错误。作者通过使用编译时依赖注入的方式解决了这个问题,并分享了解决方案。文章详细描述了问题的出现情况、错误输出内容以及解决方案的具体步骤。如果你也遇到了类似的问题,本文可能对你有一定的参考价值。 ... [详细]
  • 本文介绍了Java高并发程序设计中线程安全的概念与synchronized关键字的使用。通过一个计数器的例子,演示了多线程同时对变量进行累加操作时可能出现的问题。最终值会小于预期的原因是因为两个线程同时对变量进行写入时,其中一个线程的结果会覆盖另一个线程的结果。为了解决这个问题,可以使用synchronized关键字来保证线程安全。 ... [详细]
  • 有没有一种方法可以在不继承UIAlertController的子类或不涉及UIAlertActions的情况下 ... [详细]
author-avatar
妹纸叫BLACK
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有