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

如何同步返回ListenableFuture的方法?确保buildResult仅执行一次

解决buildResult()重复执行的并发问题

你当前的问题核心是锁只保护了Future的创建流程,但没缓存正在计算的Future——第一个线程返回Future后锁就释放了,第二个线程进来时结果还没生成,所以会再次触发createResult(),导致buildResult()执行两次。

下面给出两种可靠的解决方案:

方案一:手动缓存Future(双重检查锁)

核心思路是提前缓存正在计算的ListenableFuture,而不是等结果生成后才缓存,这样所有线程都会复用同一个Future,自然buildResult()只会执行一次。

步骤1:添加Future缓存变量

在类中新增一个volatile修饰的成员变量,保证多线程下的可见性:

private volatile ListenableFuture<Result> cachedResultFuture;

步骤2:修改getOrCreateResult()方法

用双重检查锁机制,确保只有第一个线程会创建Future,后续线程直接复用缓存的Future:

public ListenableFuture<Result> getOrCreateResult() {
    // 非同步检查,减少锁竞争
    ListenableFuture<Result> future = cachedResultFuture;
    if (future != null) {
        return future;
    }

    synchronized (this) {
        // 同步块内再次检查,避免并发创建
        future = cachedResultFuture;
        if (future == null) {
            future = createResult();
            cachedResultFuture = future;
        }
    }
    return future;
}

步骤3:确保结果持久化

在createResult()的transform阶段,完成buildResult()后要把结果存入getResult()能读取到的存储(比如成员变量、数据库等),保证后续调用getResult()能拿到已生成的结果:

private ListenableFuture<Result> createResult() {
    return FluentFutures.from(this::futureComputation1)
        .transformAsync(this::futureComputation2, executor)
        .transformAsync(this::futureComputation3, executor)
        // ... 其他异步计算步骤
        .transform(someComputation -> {
            Result result = buildResult();
            // 将结果存入getResult()对应的存储结构
            saveResult(result);
            return result;
        }, executor);
}

方案二:用Guava LoadingCache简化实现

如果项目中已经引入Guava,可以直接用LoadingCache,它内置了并发控制,自动保证load方法只执行一次:

步骤1:初始化LoadingCache

private final LoadingCache<Void, Result> resultCache = CacheBuilder.newBuilder()
    // 可根据需求配置缓存过期时间、最大容量等参数
    .build(new CacheLoader<Void, Result>() {
        @Override
        public Result load(Void key) throws Exception {
            // 执行异步计算并阻塞获取结果
            return createResult().get();
        }
    });

步骤2:实现getOrCreateResult()

如果需要返回ListenableFuture,使用Guava 19+提供的getAsync方法:

public ListenableFuture<Result> getOrCreateResult() {
    return resultCache.getAsync(null);
}

如果不需要返回Future,直接用getUnchecked获取结果即可:

public Result getOrCreateResult() {
    return resultCache.getUnchecked(null);
}

方案有效性说明

  • 方案一通过缓存Future本身,让所有线程共享同一个正在计算的任务,即使第一个线程还没完成计算,后续线程也只是等待同一个Future的结果,不会重复触发buildResult()。
  • 方案二的LoadingCache内部已经实现了成熟的并发控制逻辑,省去了手动处理双重检查锁的细节,代码更简洁易维护。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 14:36:25