本地缓存-LoadingCache

760 阅读3分钟

一、使用场景

缓存的作用不言而喻就是提高查询效率,本地缓存就是利用空间换时间的一种获取资源的方式,像我们自己维护一个ConcurrentMap,就实现了最简单的本地缓存,但是需要我们自己去做一些缓存的策略,例如缓存过期、缓存清除等该怎么做。那么相应的就会出现一些本地缓存框架,本文主要记录LoadingCache的使用。

想我之前的一家公司会在项目启动的时候加载一些配置信息和枚举值到Map中,然后会开放接口用于新增、刷新缓存。

二、基本使用

依赖:

<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>30.1.1-jre</version>
</dependency>

例子:

private static LoadingCache<Integer, String> localCache = CacheBuilder.newBuilder()
            // 缓存大小,缓存项接近该大小时,开始回收
            .maximumSize(9)
            // 缓存项在给定时间内没有被读/写访问,则回收
//            .expireAfterAccess(5, TimeUnit.MILLISECONDS)
            // 定时刷新,设置时间后,当有访问时会重新执行load方法重新加载
            .refreshAfterWrite(10, TimeUnit.MILLISECONDS)
            // 移除监听器,缓存项被移除时触发
            .removalListener(new RemovalListener<Integer, String>() {
                @Override
                public void onRemoval(RemovalNotification<Integer, String> removalNotification) {
                    System.out.println("被移除的键:"+removalNotification.getKey()+",值"+removalNotification.getValue()+",移除原因:"+removalNotification.getCause());
                }
            })
            .build(new CacheLoader<Integer, String>() {
                @Override
                public String load(Integer key) throws Exception {
                    System.out.println("key:" + key);
                    return String.valueOf(key);
                }
            });


    public static void main(String[] args) throws ExecutionException, InterruptedException {
        for (int i = 1; i< 20; i++) {
            localCache.get(i);
        }
    }

三、常用方法

3.1 基础方法

  1. maximumSize:当容量超出指定值时缓存尝试回收最近没有使用或总体上很少使用的缓存项
    看了很多文章都说在缓存项接近该大小的时候开始回收,测试之后发现都是在达到该大小后继续存储才会回收,有知道的同学请留言区告知下~
  2. expireAfterAccess(long, TimeUnit):缓存项在给定时间内没有被读/写访问,则回收。请注意这种缓存的回收顺序和基于大小回收一样
  3. expireAfterWrite(long, TimeUnit):缓存项在给定时间内没有被写访问(创建或覆盖),则回收。如果认为缓存数据总是在固定时候后变得陈旧不可用,这种回收方式是可取的。
  4. refreshAfterWrite(long, TimeUnit):定时刷新,可以为缓存增加自动定时刷新功能

注意⚠️:refreshAfterWrite通过定时刷新可以让缓存项保持可用,但只有在被检索时才会真正刷新,即只有刷新时间间隔到了再去get(key)操作才会重新执行load操作。所以如果同时配置了expireAfterWrite和refreshAfterWrite,当缓存项过期回收后,不会因为刷新操作盲目的定时重置缓存项。

3.2 缓存回收

  1. 基于容量的回收
    CacheBuilder.maximumSize(long)

  2. 定时回收(2种)

    1.  expireAfterAccess(long, TimeUnit):缓存项在给定时间内没有被读/写访问,则回收。
    2. expireAfterWrite(long, TimeUnit):缓存项在给定时间内没有被写访问(创建或覆盖),则回收。
  3. 基于引用的回收
    此处理解不到位,可看参考文章:java 缓存架构剖析–本地缓存(LoadingCache)_莫浔的博客-CSDN博客

3.3 显示清除

  • 单个清除:Cache.invalidate(key)
  • 批量清除:Cache.invalidate(keys)
  • 清除所有:Cache.invalidateAll()

3.4 移除监听器

CacheBuilder.removalListener(RemovalListener),声明一个监听器,缓存项被移除时做一些额外操作,移除时会通知removalNotification,其中包含移除键、值以及移除原因。

默认情况下,监听器操作是在移除缓存时同步调用的,因为缓存的维护和请求响应是同时执行的,监听器方法在同步模式下会拖慢正常的缓存请求,可以使用RemovalListeners.asynchronous(RemovalListener,Executor)把监听器装饰为异步操作。

3.5 刷新

LoadingCache.refresh():为键加载新值,这个过程是异步的,在刷新操作时,缓存依然可以向其他线程返回旧值,而不像回收操作,读缓存的线程必须等待新值加载完成。

参考文章:

java 缓存架构剖析–本地缓存(LoadingCache)_莫浔的博客-CSDN博客