java缓存工具类

399 阅读3分钟

概述 该缓存工具类、主要解决了缓存数据自动刷新的问题。在很多场景下、我们需要将一些代价昂贵的操作(例如数据库查询、远程接口调用或者复杂计算)的结果缓存起来、避免频繁重复执行、但同时又需要保证缓存数据不会长时间过时。CacheUtils提供了两种构建LoadingCache的方法:

1.异步刷新缓存

通过asyncReloading、实现了当缓存数据达到设定的过期时间后、自动在后台线程池中刷新数据。这样、如果一个线程请求数据时、发现缓存即将生效或者正在刷新、它可以先返回旧数据、而不必等待新数据加载完毕、从而提升系统响应速度。

2.同步刷新缓存 当缓存过期时,新请求会阻塞等待最新数据加载完成、然后返回最新数据。这种方式适用于缓存数据与当前线程上下文 (比如ThreadLocal)的关联性较强的场景。

    public class CacheUtils {
        /**
         * 构建异步刷新的 LoadingCache 对象
         *
         * 注意:如果你的缓存和 ThreadLocal 有关系,要么自己处理 ThreadLocal 的传递,要么使用 {@link #buildCache(Duration, CacheLoader)} 方法
         *
         * 或者简单理解:
         * 1、和“人”相关的,使用 {@link #buildCache(Duration, CacheLoader)} 方法
         * 2、和“全局”、“系统”相关的,使用当前缓存方法
         *
         * @param duration 过期时间
         * @param loader  CacheLoader 对象
         * @return LoadingCache 对象
         */
        public static <K, V> LoadingCache<K, V> buildAsyncReloadingCache(Duration duration, CacheLoader<K, V> loader) {
            return CacheBuilder.newBuilder()
                    // 只阻塞当前数据加载线程,其他线程返回旧值
                    .refreshAfterWrite(duration)
                    // 通过 asyncReloading 实现全异步加载,包括 refreshAfterWrite 被阻塞的加载线程
                    .build(CacheLoader.asyncReloading(loader, Executors.newCachedThreadPool())); 
        }

        /**
         * 构建同步刷新的 LoadingCache 对象
         *
         * @param duration 过期时间
         * @param loader  CacheLoader 对象
         * @return LoadingCache 对象
         */
        public static <K, V> LoadingCache<K, V> buildCache(Duration duration, CacheLoader<K, V> loader) {
            return CacheBuilder.newBuilder().refreshAfterWrite(duration).build(loader);
        }
    }

实例演示

假设我们有一个场景:从数据库中查询用户信息,但查询操作较慢,我们希望将用户数据缓存起来,并且定时刷新数据。下面是一个简单的例子:

package util.cache;

import com.byd.common.util.cache.CacheUtils;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import lombok.SneakyThrows;
import org.junit.jupiter.api.Test;

import java.time.Duration;

public class CacheTest {
    /**
     *  1.模拟一个耗时的数据库查询方法
     */
    @SneakyThrows
    public static String queryFromDB(String key) {
        Thread.sleep(2000);
        return "Key:"+key;
    }

    @Test
    @SneakyThrows
    public void test() {
        // 使用异步刷新缓存、设置缓存过期时间为10秒
        LoadingCache<String, String> userCache = CacheUtils.buildAsyncReloadingCache(Duration.ofSeconds(10), new CacheLoader<>() {
            @Override
            public String load(String key) throws Exception {
                System.out.println("load key:" + key);
                return queryFromDB(key);
            }
        });
        // 第一次获取数据、会触发 load 方法、延迟2秒
        System.out.println("User 1:"+userCache.get("user1")); //user1 键本身不携带任何信息,只是为了触发缓存加载。
        // 后续的调用、如果在10秒内,不会再次加载
        System.out.println("User 1:"+userCache.get("user1"));
        // 等待 12 秒、让缓存过期并触发异步刷新
        Thread.sleep(12000);
        // 再次获取数据、仍然返回旧数据,同时在后台刷新
        System.out.println("User 1 (after 12s):"+userCache.get("user1"));
        // 等待 3 秒、让后台刷新完成
        Thread.sleep(3000);
        // 再次获取数据、返回新数据
        System.out.println("User 1 (refreshed):"+userCache.get("user1"));
    }

}

图片.png

说明

  • 初次加载:调用 userCache.get(1) 时,由于缓存中没有对应数据,会调用 load 方法,从数据库中查询用户信息,此时会有大约 2 秒的延迟。
  • 缓存命中:在缓存有效期内(10 秒),多次调用 get 方法,直接返回缓存中的数据。
  • 异步刷新:当缓存超过 10 秒后,下一次请求会先返回旧数据,并在后台异步刷新最新数据。这样不会阻塞请求线程。
  • 数据更新:等待后台刷新完成后,后续的请求将返回最新的用户信息。

通过这种方式,CacheUtils 能够平衡数据的新鲜度和系统的响应速度,避免了频繁的数据库查询,同时又能确保缓存数据在后台得到及时更新。