You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何创建使用缓存值的CompletableFuture?基于Guava缓存经验的异步需求

嘿,这个问题我太有发言权了!之前用Guava处理异步缓存的时候踩过一模一样的坑——直接把CompletableFuture塞进Suppliers.memoize()里,结果并发请求一来,同一个key触发了N次异步任务,完全没起到缓存的作用。接下来我给你拆解问题,再给两个靠谱的解决方案:

先搞清楚你担心的核心问题

当你用Supplier<CompletableFuture<Integer>> cache = Suppliers.memoize(...)时,问题出在:如果多个线程同时调用cache.get(),而此时缓存还没初始化,Supplier的lambda会被多次执行,每次都创建一个新的CompletableFuture,相当于同时启动了多个异步计算任务,这显然不是我们要的缓存效果。而且如果其中一个Future失败了,后续所有调用都会拿到这个失败的Future,除非缓存过期。

方案一:用Guava LoadingCache(推荐)

LoadingCache天生就是为这种并发缓存场景设计的——同一个key下,只会有一个线程执行加载逻辑,其他线程会等待这个加载完成,完美解决重复异步任务的问题。而且它支持丰富的缓存配置(过期时间、最大容量、移除监听等),处理异常也更灵活。

代码示例:

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

public class AsyncCacheExample {
    private final LoadingCache<String, CompletableFuture<Integer>> asyncCache;

    public AsyncCacheExample() {
        asyncCache = CacheBuilder.newBuilder()
                // 缓存5分钟后过期
                .expireAfterWrite(5, TimeUnit.MINUTES)
                // 可选:设置缓存最大容量,避免内存溢出
                .maximumSize(1000)
                // 可选:移除缓存时取消未完成的异步任务
                .removalListener(notification -> {
                    CompletableFuture<Integer> future = notification.getValue();
                    if (!future.isDone()) {
                        future.cancel(true);
                    }
                })
                .build(new CacheLoader<String, CompletableFuture<Integer>>() {
                    @Override
                    public CompletableFuture<Integer> load(String key) throws Exception {
                        // 这里写你的异步计算逻辑,比如调用远程接口、数据库查询
                        return CompletableFuture.supplyAsync(() -> {
                            // 模拟耗时操作
                            try {
                                Thread.sleep(1000);
                            } catch (InterruptedException e) {
                                Thread.currentThread().interrupt();
                                return -1;
                            }
                            return Integer.parseInt(key);
                        });
                    }
                });
    }

    // 对外提供的获取方法
    public CompletableFuture<Integer> getValue(String key) {
        try {
            return asyncCache.get(key);
        } catch (Exception e) {
            // 处理缓存加载异常,比如返回失败的Future
            return CompletableFuture.failedFuture(e);
        }
    }
}

这个方案的优势:

  • 并发安全:同一key只会触发一次异步加载,其他请求等待结果
  • 异常处理灵活:可以在load方法里给Future添加异常回调,或者在getValue里统一处理加载异常
  • 缓存策略丰富:支持过期、容量限制、异步刷新(refreshAfterWrite)等
  • 资源清理:通过removalListener可以取消未完成的异步任务,避免资源浪费

方案二:用Suppliers.memoizeWithExpiration(适合简单场景)

如果你坚持要用Suppliers,得确保Supplier只创建一次CompletableFuture——也就是memoize的是Future本身,而不是每次调用都新建。但要注意,这个方案的局限性在于:如果Future失败了,缓存会一直保留失败的Future,直到过期;而且没有内置的并发控制(不过memoize本身是线程安全的,所以同一时间只会有一个线程执行Supplier的逻辑)。

代码示例:

import com.google.common.base.Suppliers;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

public class AsyncSupplierExample {
    // 缓存5分钟,线程安全
    private final Supplier<CompletableFuture<Integer>> asyncSupplier = Suppliers.memoizeWithExpiration(() -> {
        // 这个逻辑只会被执行一次(直到缓存过期)
        return CompletableFuture.supplyAsync(() -> {
            // 耗时计算
            return 42;
        }).exceptionally(ex -> {
            // 可选:处理异步任务的异常
            System.err.println("任务失败:" + ex.getMessage());
            return -1;
        });
    }, 5, TimeUnit.MINUTES);

    public CompletableFuture<Integer> getValue() {
        return asyncSupplier.get();
    }
}

注意事项:

  • 这个方案适合单key的场景(比如全局配置),如果是多key的缓存,还是LoadingCache更合适
  • 若异步任务失败,后续调用会一直拿到失败的结果,直到缓存过期。如果需要失败后重试,得自己加逻辑(比如在Future里重试,或者手动清空缓存)

最后总结

如果你需要多key、灵活的缓存策略和完善的并发控制,优先选LoadingCache;如果是简单的单key异步缓存,用Suppliers.memoizeWithExpiration也可以,但要注意异常处理的坑。

内容的提问来源于stack exchange,提问作者Gili

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 07:14:40