Caffeine使用分享

Caffeine是一个高性能的Java缓存库,类似Guava Cache,支持建造者模式定制各种特性,如异步加载、基于大小或时间的过期策略、手动或自动移除、监听器及统计等。本文详细介绍了Caffeine的使用方法,包括配置、加载策略、过期策略、缓存移除和统计等功能,并提供了示例代码。
摘要由CSDN通过智能技术生成

缓存分类

        缓存又分进程内缓存和分布式缓存两种:分布式缓存如redis、memcached等,还有本地(进程内)缓存如ehcache、GuavaCache、Caffeine等

Caffeine简介

        Caffeine是一个基于Java8开发的高性能Java缓存库,可提供接近最佳的命中率,其结构和 Guava Cache 基本一样,api也一样,基本上很容易就能替换。 Caffeine 实际上就是在 Guava Cache 的基础上,利用了一些 Java 8 的新特性,提高了某些场景下的性能效率。缓存与ConcurrentMap相似,但并不完全相同。最根本的区别是ConcurrentMap会保留添加到其中的所有元素,直到将其明确删除为止。

Caffeine可以通过建造者模式灵活的组合以下特性:

  1. 通过异步自动加载实体到缓存中
  2. 基于大小的回收策略
  3. 基于时间的回收策略
  4. 自动刷新
  5. key自动封装虚引用
  6. value自动封装弱引用或软引用
  7. 实体过期或被删除的通知
  8. 写入外部资源
  9. 统计累计访问缓存

如何使用

在项目pom.xml中添加Caffeine的依赖

 <!-- https://mvnrepository.com/artifact/com.github.ben-manes.caffeine/caffeine -->
 <dependency>
     <groupId>com.github.ben-manes.caffeine</groupId>
     <artifactId>caffeine</artifactId>
     <version>2.8.6</version>
 </dependency>

缓存加载

Caffeine提供了四种缓存添加策略:手动加载,自动加载,手动异步加载和自动异步加载。

手动加载

Caffeine 有两种方式限制缓存大小。两种配置互斥,不能同时配置

  1. 创建一个限制容量 Cache
Cache<String, String> cache = Caffeine.newBuilder()
 		// 设置超时时间为5s,超过该时间缓存过期
        .expireAfterWrite(5, TimeUnit.SECONDS)
        // 设置缓存最大条目数,超过条目则触发回收。
        .maximumSize(1)
        .build();

需要注意的是,实际实现上为了性能考虑,这个限制并不会很死板:

  • 在缓存元素个数快要达到最大限制的时候,过期策略就开始执行了,所以在达到最大容量前也许某些不太可能再次访问的 Entry (Key-Value)就被过期掉了。
  • 有时候因为过期 Entry 任务还没执行完,更多的 Entry 被放入缓存,导致缓存的 Entry 个数短暂超过了这个限制

示例:

 /**
 * 手动加载cache
 */
@Test
public void testManualLoadCache2() throws InterruptedException {
   
    Cache<String, String> cache = Caffeine.newBuilder()
    		// 设置超时时间为5s,超过该时间缓存过期
            .expireAfterWrite(5, TimeUnit.SECONDS) 
            // 设置缓存最大条目数,超过条目则触发回收。
            .maximumSize(1)
            .build();
    // 查找一个缓存元素, 没有查找到的时候返回null
    String value = cache.getIfPresent("test");
    //执行结果--> null
    System.out.println(value);
    // 查找缓存,如果缓存不存在则生成缓存元素,  如果无法生成则返回null
    value = cache.get("test", k -> "test-value");
    //执行结果--> test-value
    System.out.println(cache.getIfPresent("test"));
    //执行结果--> test-value
    System.out.println(value);


    // 加入一些缓存数据
    List<String> list = new ArrayList<>();
    for (int i = 2; i < 10; i++) {
   
        list.add("test" + i);
    }
    for (int i = 2; i < 10; i++) {
   
        // 添加或者更新一个缓存元素
        cache.put("test" + i, "test-value" + i);
    }

    // 执行缓存回收
    // 缓存的删除策略使用的是惰性删除和定时删除,但是我也可以自己调用cache.cleanUp()方法手动触发一次回收操作。cache.cleanUp()是一个同步方法。
    cache.cleanUp();

    //根据key list去获取一个map的缓存
    Map<String, String> dataObjectMap
            = cache.getAllPresent(list);
    //查看缓存中的数据
    //执行结果--> 1
    System.out.println(dataObjectMap.size()); 
    //执行结果--> {test9=test-value9}
    System.out.println(dataObjectMap); 
	//设置10s的睡眠时间,使得超过过期时间
    Thread.sleep(10000); 
    //执行结果--> null
    System.out.println(cache.getIfPresent("test"));
}
  1. 创建一个自定义权重限制容量的 Cache
 Cache<String, List<Object>> stringListCache = Caffeine.newBuilder()
                //最大weight值,当所有entry的weight和快达到这个限制的时候会发生缓存过期,剔除一些缓存
                .maximumWeight(1)
                //每个 Entry 的 weight 值
                .weigher(new Weigher<String, List<Object>>() {
   
                    @Override
                    public @NonNegative int weigh(@NonNull String key, @NonNull List<Object> value) {
   
                        return value.size();
                    }
                })
                .build();

上面我们的 value 是一个 list,以 list 的大小作为 Entry 的大小。当把 Weigher 实现为只返回1,maximumWeight 其实和 maximumSize 是等效的。 同样的,为了性能考虑,这个限制也不会很死板。

示例:

@Test
public void testManualLoadCache4() {
   
    Cache<String, List<Object>> stringListCache = Caffeine.newBuilder()
            //最大weight值,当所有entry的weight和快达到这个限制的时候会发生缓存过期,剔除一些缓存
            .maximumWeight(1)
            //每个 Entry 的 weight 值
            .weigher(new Weigher<String, List<Object>>() {
   
                @Override
                public @NonNegative int weigh(@NonNull String key, @NonNull List<Object> value) {
   
                    return value.size();
                }
            })
            .build();

    stringListCache.put("test1", Collections.singletonList("test-value1"));
    stringListCache.put("test2", Arrays.asList("test-value2","test-value2"));


    stringListCache.cleanUp();

    Map<String, List<Object>> dataObjectMap = stringListCache.getAllPresent(Arrays.asList("test1","test2"));
    System.out.println(dataObjectMap.size()); // --> 1
    System.out.println(dataObjectMap); // --> {test1=[test-value1]}
}
  1. 指定初始大小
Cache<String, Object> cache = Caffeine.newBuilder()
    //指定初始大小
    .initialCapacity(1000)
    .build();

HashMap类似,通过指定一个初始大小,减少扩容带来的性能损耗。这个值也不宜过大,浪费内存。

自动加载
  • 创建LoadingCache

示例:

	@DisplayName("测试LoadingCache")
    @Test
    public void testLoadingCache() {
   
        LoadingCache<String, String> cache = Caffeine.newBuilder()
                .maximumSize(10_000)
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .build(new CacheLoader<String, String>() {
   
                    @Override
                    public @Nullable String load(@NonNull String key) throws Exception {
   
                        //默认的数据加载实现,当调用get取值的时候,如果key没有对应的值,就调用这个方法进行加载
                        System.out.println("load data --- " + key);
                        //模拟从数据库中获取数据
                        return MAP.get(key);
                    }
                });
        //第一次的时候会调用load方法
        System.out.println(cache.get("test1")); 
        //第二次不会调用load方法
        System.out.println(cache.get("test1")); 
    }
手动异步加载
  • 创建AsyncCache

示例:

	 @Test
    public void testAsyncCache() throws ExecutionException, InterruptedException {
   
        AsyncCache<String, String> cache = Caffeine.newBuilder()
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .maximumSize(10_000)
                .buildAsync();

        // 查找缓存元素,如果不存在,则异步生成
        CompletableFuture<String> value = cache.get("test1", k -> {
   
            //异步加载
            System.out.println(Thread.currentThread().getName()); // ForkJoinPool.commonPool-worker-3
            System.out.println("load cache ---" + k);
            try {
   
                Thread.sleep(3000);
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            }
            return MAP.get(k);
        });
        System.out.println(Thread.currentThread().getName()); //main
        System.out.println("=========");
        System.out.println(value.get()); //value1, 阻塞
    }

测试结果:

ForkJoinPool.commonPool-worker-3
load cache ---test1
main
=========
value1
自动异步加载
  • 创建AsyncLoadingCache

示例1:

	@Test
    public void testAsynchronouslyLoadingCache() throws ExecutionException, InterruptedException {
   
        AsyncLoadingCache<String, String> cache = Caffeine.newBuilder()
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .maximumSize(10_000)
                //异步的封装一段同步操作来生成缓存元素
                .buildAsync(new CacheLoader<String, String>() {
   
                    @Override
                    public @Nullable String load
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值