为何循环中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线程的终止时机:
- CompletableFuture默认使用
ForkJoinPool.commonPool()执行异步任务,该池中的线程是守护线程。JVM会在所有非守护线程终止后直接退出,不会等待守护线程完成任务。 - 循环版本中,main线程快速完成5次任务提交后立即终止,此时JVM开始退出,大部分异步任务还在执行(
delay()方法会阻塞1秒),因此只有少量任务能完成整个执行链并输出结果。 - 手动重复版本中,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
相关产品推荐
相关产品推荐

