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

为何循环中CompletableFuture的thenAcceptAsync无法正常输出全部结果?

CompletableFuture循环与手动重复代码的输出差异问题

问题描述

两段语义看似一致的CompletableFuture代码,运行结果却截然不同:

循环版本代码

public static void main(String... args)  {
    for (int i = 0; i < 5; i++) {
        CompletableFuture.supplyAsync(() -> Thread.currentThread().getName())
                .thenApply(threadName -> {
                    delay();
                    List<String> parsedName = Arrays.stream(threadName.split("-")).toList();
                    return parsedName.get(parsedName.size() - 1);
                })
                .thenAcceptAsync(System.out::println);
    }
}

static void delay() {
    try {
        Thread.sleep(1000);
    } catch(InterruptedException ignored) {
        System.out.println("Interrupted");
    }
}

输出:

1

手动重复版本代码

public static void main(String... args)  {
    CompletableFuture.supplyAsync(() -> Thread.currentThread().getName())
            .thenApply(threadName -> {
                delay();
                List<String> parsedName = Arrays.stream(threadName.split("-")).toList();
                return parsedName.get(parsedName.size() - 1);
            })
            .thenAcceptAsync(System.out::println);
    CompletableFuture.supplyAsync(() -> Thread.currentThread().getName())
            .thenApply(threadName -> {
                delay();
                List<String> parsedName = Arrays.stream(threadName.split("-")).toList();
                return parsedName.get(parsedName.size() - 1);
            })
            .thenAcceptAsync(System.out::println);
    CompletableFuture.supplyAsync(() -> Thread.currentThread().getName())
            .thenApply(threadName -> {
                delay();
                List<String> parsedName = Arrays.stream(threadName.split("-")).toList();
                return parsedName.get(parsedName.size() - 1);
            })
            .thenAcceptAsync(System.out::println);
    CompletableFuture.supplyAsync(() -> Thread.currentThread().getName())
            .thenApply(threadName -> {
                delay();
                List<String> parsedName = Arrays.stream(threadName.split("-")).toList();
                return parsedName.get(parsedName.size() - 1);
            })
            .thenAcceptAsync(System.out::println);
    CompletableFuture.supplyAsync(() -> Thread.currentThread().getName())
            .thenApply(threadName -> {
                delay();
                List<String> parsedName = Arrays.stream(threadName.split("-")).toList();
                return parsedName.get(parsedName.size() - 1);
            })
            .thenAcceptAsync(System.out::println);
}

static void delay() {
    try {
        Thread.sleep(1000);
    } catch(InterruptedException ignored) {
        System.out.println("Interrupted");
    }
}

输出:

1
1
1
1
1

原因分析

核心差异源于JVM线程的守护特性和main线程的终止时机:

  1. CompletableFuture默认使用ForkJoinPool.commonPool()执行异步任务,该池中的线程是守护线程。JVM会在所有非守护线程终止后直接退出,不会等待守护线程完成任务。
  2. 循环版本中,main线程快速完成5次任务提交后立即终止,此时JVM开始退出,大部分异步任务还在执行(delay()方法会阻塞1秒),因此只有少量任务能完成整个执行链并输出结果。
  3. 手动重复版本中,main线程执行5段代码的时间更长,给了所有异步任务足够的时间完成supplyAsync、thenApply和thenAcceptAsync的整个流程,因此能输出全部5个结果。

验证与解决方法

如果想让循环版本也输出全部结果,只需让main线程等待所有异步任务完成:

  • 方法一:收集所有CompletableFuture,用allOf等待
public static void main(String... args)  {
    List<CompletableFuture<Void>> futures = new ArrayList<>();
    for (int i = 0; i < 5; i++) {
        CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> Thread.currentThread().getName())
                .thenApply(threadName -> {
                    delay();
                    List<String> parsedName = Arrays.stream(threadName.split("-")).toList();
                    return parsedName.get(parsedName.size() - 1);
                })
                .thenAcceptAsync(System.out::println);
        futures.add(future);
    }
    // 等待所有任务完成
    CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
}
  • 方法二:让main线程休眠足够时间
public static void main(String... args) throws InterruptedException {
    for (int i = 0; i < 5; i++) {
        CompletableFuture.supplyAsync(() -> Thread.currentThread().getName())
                .thenApply(threadName -> {
                    delay();
                    List<String> parsedName = Arrays.stream(threadName.split("-")).toList();
                    return parsedName.get(parsedName.size() - 1);
                })
                .thenAcceptAsync(System.out::println);
    }
    // 等待所有任务执行完成
    Thread.sleep(2000);
}

内容的提问来源于stack exchange,提问作者Mehmet Atakan Serin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 07:36:04