谷歌Java开发工具包之Cache缓存源码分析(一)
前言
本篇文章是对Google的Guava中Cache进行一个源码级的分析,因为篇幅有限,而单单就LocalCache的量就达到了5000+行,还是有很多细节无法展现出来,富文本格式代码展示不太容易,就用了图片来替换.尽量让格式看起来好点.
文章包括两部分:
-
一: 前言扩展,官方文档及基础架构设计
-
二. 通过源码阅读来分析其数据结构,cache命中率等状态,数据引用类型,使用. 犹豫篇幅较长建议收藏阅读 ! 如果看完觉得还有不明白的地方,可以在下方留言.本篇文章是从全局的进行阅读分析,第二篇会从锁及并发方面分析其性能,感兴趣的同学,可以关注下
官方介绍
关键词及API科普
-
LRU (Least Recently Used): 最近最少使用,常用与回收策略
-
void invalidate(Object key); 删除缓存
-
void invalidateAll(); 清楚所有的缓存,相当远map的clear操作。
-
long size(); 获取缓存中元素的大概个数。为什么是大概呢?元素失效之时,并不会实时的更新size,所以这里的size可能会包含失效元素。
-
CacheStats stats(); 缓存的状态数据,包括(未)命中个数,加载成功/失败个数,总共加载时间,删除个数等。
GITHUB-WIKI文档:
https://github.com/google/guava/wiki/CachesExplained
官方使用介绍:
Generally, the Guava caching utilities are applicable whenever:
-
You are willing to spend some memory to improve speed.
-
You expect that keys will sometimes get queried more than once.
-
Your cache will not need to store more data than what would fit in RAM. (Guava caches are local to a single run of your application. They do not store data in files, or on outside servers. If this does not fit your needs, consider a tool like Memcached.)
翻译:
-
愿意消耗一些内存空间来提升速度;
-
能够预计某些key会被查询一次以上;
-
缓存中存放的数据总量不会超出内存容量(`Guava Cache`是单个应用运行时的本地缓存)。
实现原理
Cache本质上就是ConcurrentMap,区别是:Cache除了拥有Map集合属性,为了限制内存使用还拥有可设置的自动移除的策略,而ConcurrentMap只是数据结构,会一直保存数据,直到显式移除.
Guava Cache提供了三种基本的缓存回收方式:
基于容量回收、定时回收和基于引用回收。
定时回收有两种:按照写入时间,最早写入的最先回收;按照访问时间,最早访问的最早回收。
Guava设计架构分析
CacheBuilder
提供了build方法的两种返回类型,其分别创建两种类型的缓存器,这两种类型的缓存器都属于LocalCache的内部类,
CacheBuilder的内部属性(下文有详细讲解),会为LocalCache缓存的生命周期等操作赋值(注意看构造中参数):
1. 自动从数据源加载到缓存中 其是实现LoadingCache接口是对Cache接口的一个扩展,eg:实现了自动从CacheLoading中加载数据
2. 当调用get方法从Callbale参数中同步得到,其是实现了Cache接口
下图是分别实现了上面那些接口的缓存类
通过上面我们知道Guava的Cache有两种方式去读缓存
1. get(key) 获取,当获取不到的时候,就从构造中提前指定的loader中获取
2. get(key,Callable loader) 当从缓存中获取不到的时候,就从Callable中获取
那么我们现在开始分析,Guava是如何通过接口的形式来定义这个呢?
首先我们看Cache接口和LoadingCache
我们可以想到当如果是继承了Cache接口就要实现从Callable中获取,如果继承了LoadingCache就可以提前从创建时候指定的CacheLoader中获取.
当我们对接口有一个正确的认识的时候,我们开始关注缓存的基础实现类
LocalCache
LocalCache是缓存的最终实现类,大部分逻辑代码都在改类中编写通过该类分析,该类中包含了很多的静态内部类和接口定义建议阅读源码学习
通过其是继承ConcurrentMap可知,Guava的缓存其实就是ConcurrentMap其利用分段锁的特性,来提高性能.
其提供了两个内部类
-
LocalLoadingCache内部类
2. LocalManualCache内部类
问题解答
当你看到这里,相信你对Guava的设计架构已经有一个大概的认识,现在我们开始了解一下细节的问题
eg:
1. CacheBuilder 中都默认有那些属性?
2. Cache的CacheStats状态及Hit热度及命中率是如何统计的?
3. Cache的数据格式和ConcurrentMap是一样的,那么他的数据结构到底是什么样的?来提高效率的?
4. Cache是如果做到强引用和弱引用及软应用的?
5. 最后我们在看我们应该如何正确的使用这个呢?
CacheBuilder 中都默认有那些属性?
CacheBuilder 中有一个非常值得我们学习的小细节,这个参数检查是不是非常优美!
(首先设置默认值,当,this.值不等于默认值,说明已经赋值过,在赋值就报错),之所以说是细节,是因为大部分人不会注意到,学到就要用到奥!
Cache的CacheStats状态及Hit热度及命中率是如何统计的?
我们看LocalCache内部接口StatsCounter是怎么定义的 !
当我们对缓存状态的接口掌握后,我们开始猜测他调用的时机
统计加载时间
统计清理次数
Cache的数据格式和ConcurrentMap是一样的,那么他的数据结构到底是什么样的?来提高效率的?
我们知道ConcurrentMap的数据结构是分段,通过分段锁的方式来提高效率的
现在我们看LocalCache的数据结构,
我们看LocalCache的内部类Segment,即段
每个Segment段里面,有一个AtomicReferenceArray数组,数组中每个元素即:ReferenceEntry,是一个队列
抽查一个他的实现类
UML图
Cache是如何提高效率的呢?ConcurrentHashMap是如何提高效率的呢?
单单从数据结构上来分析(LocalCache和ConcurrentHashMap原理一样),我们知道一个ConcurrentHashMap由多个segment组成,每个segment包含一个Entity的数组。这里比HashMap多了一个segment类。该类继承了ReentrantLock类,所以本身是一个锁。当多线程对ConcurrentHashMap操作时,不是完全锁住map,而是锁住相应的segment。这样提高了并发效率. Cache和其类似,所以就不多说了.另外Cache提供了多种引用类型,及自动回收的方式.
Cache是如果做到强引用和弱引用及软应用的?
我的步骤是这样的,因为当我们在添加缓存的时候,都默认是Strong强引用,guava既然支持引入回收,那么他肯定是在报错的时候,对我们的数据,进行了wrap
包装(pool数据池化分析里面的思路),那么我们就从put作为入口,开始分析数据结构.
首先我们先猜测一下put会做哪些操作:
1. 根据key 哈希找到区段 入参中获取hash
2. 检查容量,执行清理 expand() evictEntries(newEntry)
3. 如果不存在,就包装为指定的引用值 [重点分析] setValue
在setValue中我们找到了包装的代码:即将缓存的key和value包装为用户设置cache的引用方式
在构造时候LocalCache根据CacheBuilder的属性设置
我们再来看Guava缓存中的引用是如何定义的! 这里不做详细介绍,如果不懂,可以看<<大部分Java程序猿所不了解的引用关系>>
-
强引用: Strong new出来的都是强引用
-
软引用: Soft 当jvm内存充足,gc不会回收,只有在内存不够时候回收
-
弱引用: weak 不论内存是否充足,gc都会在触发时候去回收
接下来我们来讲最后一个细节: 我们应该如何正确的使用!
相信当大家看到这里的时候,对CacheBuilder有正确的认识之后,基本就不用介绍了吧! 直接撸代码
分为两部分
1. 创建Cache
2. 创建移除事件
文章来源: springlearn.blog.csdn.net,作者:西魏陶渊明,版权归原作者所有,如需转载,请联系作者。
原文链接:springlearn.blog.csdn.net/article/details/79296241
- 点赞
- 收藏
- 关注作者
评论(0)