Caffeine 三种填充策略:手动、同步和异步

一、简介

Caffeine — 一个高性能的 Java 缓存库。缓存和 Map 之间的一个根本区别在于缓存可以回收存储的 item。回收策略为在指定时间删除哪些对象。此策略直接影响缓存的命中率 — 缓存库的一个重要特征。Caffeine 因使用 Window TinyLfu 回收策略,提供了一个近乎最佳的命中率。
二、Caffeine 为我们提供了三种填充策略:手动、同步和异步
1、手动加载(Manual)

    Cache<String, Object> manualCache = Caffeine.newBuilder()
            .expireAfterWrite(10, TimeUnit.MINUTES)
            .maximumSize(10_000)
            .build();
     
    String key = "name1";
    // 根据key查询一个缓存,如果没有返回NULL
    graph = manualCache.getIfPresent(key);
    // 根据Key查询一个缓存,如果没有调用createExpensiveGraph方法,并将返回值保存到缓存。
    // 如果该方法返回Null则manualCache.get返回null,如果该方法抛出异常则manualCache.get抛出异常
    graph = manualCache.get(key, k -> createExpensiveGraph(k));
    // 将一个值放入缓存,如果以前有值就覆盖以前的值
    manualCache.put(key, graph);
    // 删除一个缓存
    manualCache.invalidate(key);
     
    ConcurrentMap<String, Object> map = manualCache.asMap();
    cache.invalidate(key);

Cache接口允许显式的去控制缓存的检索,更新和删除。
我们可以通过cache.getIfPresent(key) 方法来获取一个key的值,通过cache.put(key, value)方法显示的将数控放入缓存,
但是这样子会覆盖缓原来key的数据。更加建议使用cache.get(key,k - > value) 的方式,get 方法将一个参数为 key 的
 Function (createExpensiveGraph) 作为参数传入。如果缓存中不存在该键,则调用这个 Function 函数,并将返回值作为
该缓存的值插入缓存中。get 方法是以阻塞方式执行调用,即使多个线程同时请求该值也只会调用一次Function方法。
这样可以避免与其他线程的写入竞争,这也是为什么使用 get 优于 getIfPresent 的原因。

注意:如果调用该方法返回NULL(如上面的 createExpensiveGraph 方法),则cache.get返回null,如果调用该方法抛出异常,则get方法也会抛出异常。可以使用Cache.asMap() 方法获取ConcurrentMap进而对缓存进行一些更改。
2、同步加载(Loading)

    LoadingCache<String, Object> loadingCache = Caffeine.newBuilder()
            .maximumSize(10_000)
            .expireAfterWrite(10, TimeUnit.MINUTES)
            .build(key -> createExpensiveGraph(key));
        
    String key = "name1";
    // 采用同步方式去获取一个缓存和上面的手动方式是一个原理。在build Cache的时候会提供一个createExpensiveGraph函数。
    // 查询并在缺失的情况下使用同步的方式来构建一个缓存
    Object graph = loadingCache.get(key);
     
    // 获取组key的值返回一个Map
    List<String> keys = new ArrayList<>();
    keys.add(key);
    Map<String, Object> graphs = loadingCache.getAll(keys);

LoadingCache是使用CacheLoader来构建的缓存的值。批量查找可以使用getAll方法。默认情况下,getAll将会对缓存中没有值的key分别调用CacheLoader.load方法来构建缓存的值。我们可以重写CacheLoader.loadAll方法来提高getAll的效率。

注意:您可以编写一个CacheLoader.loadAll来实现为特别请求的key加载值。
例如,如果计算某个组中的任何键的值将为该组中的所有键提供值,则loadAll可能会同时加载该组的其余部分。
3、异步加载(Asynchronously Loading)

    AsyncLoadingCache<String, Object> asyncLoadingCache = Caffeine.newBuilder()
                .maximumSize(10_000)
                .expireAfterWrite(10, TimeUnit.MINUTES)
                // Either: Build with a synchronous computation that is wrapped as asynchronous
                .buildAsync(key -> createExpensiveGraph(key));
                // Or: Build with a asynchronous computation that returns a future
                // .buildAsync((key, executor) -> createExpensiveGraphAsync(key, executor));
     
     String key = "name1";
     
    // 查询并在缺失的情况下使用异步的方式来构建缓存
    CompletableFuture<Object> graph = asyncLoadingCache.get(key);
    // 查询一组缓存并在缺失的情况下使用异步的方式来构建缓存
    List<String> keys = new ArrayList<>();
    keys.add(key);
    CompletableFuture<Map<String, Object>> graphs = asyncLoadingCache.getAll(keys);
    // 异步转同步
    loadingCache = asyncLoadingCache.synchronous();

AsyncLoadingCache是继承自LoadingCache类的,异步加载使用Executor去调用方法并返回一个CompletableFuture。异步加载缓存使用
了响应式编程模型。如果要以同步方式调用时,应提供CacheLoader。要以异步表示时,应该提供一个AsyncCacheLoader,并返回一个CompletableFuture。synchronous()这个方法返回了一个LoadingCacheView视图,LoadingCacheView也继承自LoadingCache。调用该方法后就相当于你将一个异步加载的缓存AsyncLoadingCache转换成了一个同步加载的缓存LoadingCache。

默认使用ForkJoinPool.commonPool()来执行异步线程,但是我们可以通过Caffeine.executor(Executor) 方法来替换线程池。

 
————————————————
版权声明:本文为CSDN博主「饥饿小猪」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Z0157/article/details/83663129
posted on 2023-01-31 11:43  duanxz  阅读(1132)  评论(0编辑  收藏  举报