如果链中的第一个方法是异步的,那么一系列方法调用 (CompletableFuture API) 是否会异步执行?

问题描述

我正在学习 CompletableFuture API,有一个例子:

CompletableFuture.completedFuture(url)
                 .thenComposeAsync(this::readPage,executor)
                 .thenApply(this::getimageURLs)
                 .thenApply(this::saveFoundImages)
                 .....

我有一个问题:如果我将 thenComposeAsync(...) 方法作为第一个调用,链中的其他方法是否会在我通过参数传递的 executor 中执行,或者我应该调用使用 async 在特定执行器中获取异步执行的其他方法

解决方法

好的,CompletableFuture 中有 3 种方法。例如:

  • thenApply()
  • thenApplyAsync(Function)(没有执行器)
  • thenApplyAsync(Function,Executor)(有一个 Executor)

last 表示此操作将在您传递给它的 Executor 中执行,这是最明显的一个。

second 表示在 ForkJoinPool 中执行操作。

第一个要有趣得多。该文档听起来很简单,通过:

为非异步方法的依赖完成提供的操作可以由完成当前 CompletableFuture 的线程执行,或者由完成的任何其他调用者执行方法

你需要开始把它分成更小的部分。您需要了解的是,有些线程完成某个CompletableFuture,有些线程对它执行某些操作,有些线程 某些依赖动作。可能,这些都是不同的线程。这就是一切的开始:

  • 如果依赖动作已经被链接,调用 complete 的线程将成为执行该动作的线程。

  • 如果未来已经完成,那么链接动作的线程将执行它。

由于上述步骤没有线性操作,因此实际上无法确定您的 thenApply 将在哪个线程中执行,至少 100% 确定。该操作可以在以下任何一个中执行:

  • 调用 complete/completeExceptionally 的线程
  • 链接 thenApply 的线程
  • 调用 join/get 的线程

以上任何一种都是可能的。如果你真的想要,我做了一个相当有趣的 test here,证明了上面的一些事情。


我不是要选择另一个答案,但他提出了一个相当有趣的观点,我在乞求中也很困惑:

在您的示例中:在 .thenComposeAsync 之后,所有以下链式期货也将由执行者完成。

我们可以很容易地证明这是不正确的:

 CompletableFuture<String> future1 = CompletableFuture.completedFuture("a");
 CompletableFuture<String> future2 = future1.thenApplyAsync(x -> "b" + x,Executors.newCachedThreadPool());

 LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));

 CompletableFuture<String> future3 = future2.thenApply(x -> {
      System.out.println(Thread.currentThread().getName());
      return x + "c";
 });
 future3.join();

如果您运行它,您将看到的是 main 实际上执行的是 thenApply,而不是池中的线程。