首页 > 解决方案 > 理解 Java 的 Completable Future 的行为

问题描述

我正在学习 Java,我有一个相对简单的 Java 程序,它从如下所示的 API 端点获取数据:

public class Main {

  public static String getJSON(String u) {
    if (u == null) throw new IllegalArgumentException("URL is null.");
    try {
      URL url = new URL(u);
      URLConnection site = url.openConnection();
      InputStream is = site.getInputStream();
      Scanner scanner = new Scanner(
              new BufferedInputStream(is),
              "UTF-8");
      String resp = "";
      while (scanner.hasNextLine()) {
        resp = resp + scanner.nextLine();
      }
      return resp;
    } catch (Exception e) {
      System.out.println(e);
      return null;
    }
  }

  public static void main(String[] args) {
    CompletableFuture<String> cf = CompletableFuture.supplyAsync(() ->
      getJSON("https://jsonplaceholder.typicode.com/posts/1")
    );
    cf.thenAcceptAsync(System.out::println);
    // System.out.println(cf.join()); <=== Commenting out this line
  }

}

我希望上面的代码能打印出原始 JSON,但它什么也没做。但是,如果我包含上面已注释掉的行,则代码可以工作,但它会打印两次原始 JSON。

我的猜测是程序在thenAcceptAsync有机会完成之前就终止了,而在.join()包含阻塞功能时情况并非如此。我的猜测是否正确,如果是,我该如何解决这个问题?

标签: javaasynchronousconcurrencyjava-8future

解决方案


您的主线程没有等待服务调用的完成。您应该在 CompletableFuture 上调用 join 以等待其执行完成:

cf.thenAcceptAsync(System.out::println).join();

您可以使用以下修改后的代码版本检查行为(只需在 VM 退出时添加关闭挂钩以打印文本):

Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println("Shutting down")));

CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
    System.out.println("running...");
    String result = getJSON("https://jsonplaceholder.typicode.com/posts/1");
    System.out.println("Completed service call");
    return result;
});
cf.thenAcceptAsync(System.out::println).join();

运行上述代码时,输​​出如下:

running...
Completed service call
{  "result json here"}
Shutting down

但是,如果没有.join(),会立即出现以下输出: e running... Shutting down

简而言之,thenAcceptAsync(System.out::println)立即返回并且主线程完成,在这种情况下,在 HTTP 调用完成之前。如果你在那之后有工作要做,它会喜欢:

cf = cf.thenAcceptAsync(System.out::println);
doSomethingElse();
doYetAnotherThing();
cf.join()

join最终应该被调用,以防止 VM 过早终止,或者在必要时等待结果准备好。


推荐阅读