首页 > 解决方案 > 返回一个 CompletableFuture 而不暴露 executor 线程

问题描述

我在库中公开了一个方法,该方法返回一个 CompletableFuture。该方法的计算发生在单线程 Executor 上,这是我的瓶颈,因此我不希望任何后续工作发生在同一个线程上。

如果我使用返回“supplyAsync”结果的简单方法,我会将我宝贵的线程暴露给调用者,调用者可能会添加同步操作(例如通过 thenAccept),这可能会在该线程上占用一些 CPU 时间。

复制如下:

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CfPlayground {
    private ExecutorService preciousExecService = Executors.newFixedThreadPool(1);

    CfPlayground() {}

    private static void log(String msg) {
        System.out.println("[" + Thread.currentThread().getName() + "] " + msg);
    }

    CompletableFuture<String> asyncOp(String param) {
        return CompletableFuture.supplyAsync(() -> {
            log("In asyncOp");
            return "Hello " + param;
        }, preciousExecService);
    }

    void syncOp(String salutation) {
        log("In syncOp: " + salutation);
    }

    void run() {
        log("run");
        asyncOp("world").thenAccept(this::syncOp);
    }

    public static void main(String[] args) throws InterruptedException {
        CfPlayground compFuture = new CfPlayground();
        compFuture.run();
        Thread.sleep(500);
        compFuture.preciousExecService.shutdown();
    }
}

这确实打印:

[main] run
[pool-1-thread-1] In asyncOp
[pool-1-thread-1] In syncOp: Hello world

我发现的一个解决方案是引入另一个 Executor,并在返回 CompletableFuture 之前添加一个 no-op thenApplyAsync 与该 executor

    CompletableFuture<String> asyncOp(String param) {
        return CompletableFuture.supplyAsync(() -> {
            log("In asyncOp");
            return "Hello " + param;
        }, preciousExecService).thenApplyAsync(s -> s, secondExecService);
    }

这行得通,但感觉不是很优雅——有没有更好的方法来做到这一点?

标签: javaconcurrencycompletable-future

解决方案


没有功能可以将您的完成与相关操作的执行分开。当链接依赖动作的线程已经完成注册并且您的执行者线程完成未来时,如果没有给出其他执行者,哪个其他线程应该执行依赖动作?

您将另一个动作与不同的执行者联系起来的方法似乎是您能得到的最好的方法。但是,重要的是要注意,在异常完成的情况下,异常会在不评估传递给的函数的情况下传播thenApplywhenComplete如果调用者链接了类似、handle或的动作,这种异常传播可能再次导致线程暴露exceptionally

另一方面,您不需要指定辅助执行器,因为您可以使用不带 executor 参数的async方法来获取默认(通用 Fork/Join)池。

因此,到目前为止,链接.whenCompleteAsync((x,y) -> {})是解决您的问题的最佳方法。


推荐阅读