加载方式
Cache手动加载
1 | public static void main(String[] args) { |
当缓存的元素无法生成或者在生成的过程中抛出异常而导致生成元素失败,cache. Get()可能会返回null
当然也可以使用Cache.asMap()所暴露出来的ConcurrentMap的方法直接对缓存进行操作
LoadingCache自动加载
1 | public static void main(String[] args) { |
AsyncLoadingCache自动异步加载
1 | public static void main(String[] args) { |
可用参数
recordStats
通过使用 Caffeine.recordStats()方法可以打开数据收集功能。Cache.stats()方法将会返回一个 CacheStats 对象,其将会含有一些统计指标,比如:
- hitRate(): 查询缓存的命中率
- evictionCount(): 被驱逐的缓存数量
- averageLoadPenalty(): 新值被载入的平均耗时
这些缓存统计指标可以被基于 push/pull 模式的报告系统进行集成。基于 pull 模式的系统可以通过调用 Cache.stats() 方法获取当前缓存最新的统计快照。一个基于 push 的系统可以通过自定义一个 StatsCounter 对象达到在缓存操作发生时自动推送更新指标的目的。
1 | //返回一个ConcurrentStatsCounter实例 |
weakKeys、weakValues 和 softValues
key支持弱引用,而value则支持弱引用和软引用,需要注意的是,AsyncCache不支持软引用和弱引用。
另外需要注意的是,使用了weakKeys后,缓存Key之间的比较将会通过引用相等(==)而不是对象相等equals()去进行。原因是GC只依赖于引用相等性。(否则的话,万一A与B是equals的,A回收但B没回收,如果是以A为key进的缓存,缓存会被回收,用户拿着B却无法再次get(B)).
同理,使用了 weakValues 或 softValues 后,value 之间的比较也会通过引用相等(==) 而不是对象相等 equals()。
1 | //枚举,基于弱/软引用 |
可以使用 CaffeineSpec 提供简单的字符格式配置,不是很常用。
1 | //严格解析标识符 |
maximumSize与maximumWeight
maximumSize
缓存会通过 Window TinyLfu 算法控制整个缓存大小。关于这个算法我们在下文的原理中细讲。
maximumWeight
这种方式控制的是总权重。需要 weigher 提供为每个 entry 计算权重的方式。当我们的缓存大小不均匀时,我们可以通过这种方式控制总大小。权重计算是在其创建或更新时发生的,此后其权重值都是静态存在的。
1 | //本地缓存的最大条数,默认为未设置参数 |
expireAfterAccess 、expireAfterWrite 与expireAfter
expireAfterAccess 表示上次读写超过一定时间后过期,expireAfterWrite 表示上次创建或更新超过一定时间后过期。expireAfter 允许复杂的表达式,过期时间可以通过 entry 等外部参数确定。
至于过期淘汰的发生,是在写操作以及偶尔发生在读操作中的。过期事件的调度和触发将会在 O(1)的时间复杂度内完成。如果希望过期发生的更及时,可以通过在你的 Cache 构造器中通过 Scheduler 接口和 Caffeine.scheduler(Scheduler) 方法去指定一个调度线程代替在缓存活动中去对过期事件进行调度。
具体地说,在默认情况下,当一个缓存元素过期的时候,Caffeine 不会自动立即将其清理和驱逐。而它将会在写操作之后进行少量的维护工作,在写操作较少的情况下,也偶尔会在读操作之后进行。如果你的缓存吞吐量较高,那么你不用去担心你的缓存的过期维护问题。但是如果你的缓存读写操作都很少,可以额外通过一个线程使用 Cache.cleanUp() 方法在合适的时候触发清理操作。
1 | //表示上次创建或更新超过一定时间后过期,默认为未设置参数 |
removalListener
removal(移除)包括 eviction(驱逐:由于策略自动移除)和 invalidation(失效:手动移除)
removalListener 的操作将会异步执行在一个 Executor 上。默认的线程池实现是 ForkJoinPool.commonPool()。当然也可以通过覆盖 Caffeine.executor(Executor) 方法自定义线程池的实现。这个 Executor 同时负责 refresh 等操作。
如果希望这个操作是同步的,可以通过下文的 writer() 方法实现。
1 | //缓存删除监听器 |
writer
CacheWriter 给缓存提供了充当底层资源的门面的能力,当其与 CacheLoader 一起使用的时候,所有的读和写操作都可以通过缓存向下传播。Writers 提供了原子性的操作,包括从外部资源同步的场景。这意味着在缓存中,当一个 key 的写入操作在完成之前,后续对这个 key 的其他写操作都是阻塞的,同时在这段时间内,尝试获取这个 key 对应的缓存元素的时候获取到的也将都是旧值。如果写入失败那么之前的旧值将会被保留同时异常将会被传播给调用者。
CacheWriter 将会在缓存元素被创建,更新或者移除的时候被触发。但是当一个映射被加载(比如 LoadingCache.get),重载 (比如 LoadingCache.refresh),或者生成 (比如 Map.computeIfPresent) 的时候将不会被触发。
需要注意的是,CacheWriter 将不能用在 weak keys 或者 AsyncLoadingCache 的场景。
CacheWriter 可以用来实现 write-through 和 write-back 两种模式的缓存。也可以用来整合多级缓存,或是用来作为发布同步监听器使用。
1 | //自定义一个写入器 |
caffeine与Guava对比
首先从结果上来看,在相同的cpu负载下,Caffeine Cache的读取和写入速度由于Guava Cache,差距在4倍以上,内存占用方面,两个没有明显的差距
初始化
Caffeine、Guava都通过builder的方式进行初始化操作,生成缓存对象,通过builder方式可以生成两种缓存对象LoadingCache(同步填充),和Cache(手动填充),LoadingCache继承Cache,相比于Cache,提供了get获取值时,如果不存在值,自动通过CacheLoader和load方法家在数据并返回的功能。
caffeine cache还提供了第三种初始化方式,异步加载的方式(见上),AsyncLoadingCache是继承自LoadingCache类的,异步加载使用Executor去调用方法并返回一个CompletableFuture,相比于同步填充模式,在load数据时,使用一步线程来执行load方法,默认使用ForkJoinPool()来执行一步线程,可以通过Caffeine.executor(Executor)方法来替换线程池。
基于时间的过期驱逐策略
guava 和caffeine都支持通过两种策略来进行数据的回收策略,分别是expireAfterWrite、expireAfterAccess,此外caffeine还支持通过expireAfter来通过重新相关方法自定义过期策略,这些过期策略都是初始化时进行指
Guava提供了两种回收策略,但是他并不是通过后台监听线程进行数据的清除工作,而是在获取值时进行值回收,所以如果一个key一直不被访问,虽然设置了过期策略,它依然会一直存在
引用
https://blog.csdn.net/zhangyunfeihhhh/article/details/108105928