bigywater.tech
Java · 391

CompletableFuture 异常传播

CompletableFuture 的异常处理容易写错,因为同一个异常在不同入口会换不同的包装。这篇逐项把包装规则、沉默的失败、几个回调的语义、allOf / anyOf 的失败时机,以及取消与超时的实际效果都量了一遍。

CompletionException
supplyAsync / thenApply 里抛出的异常在 handle、exceptionally、join 里的形状(原始异常在 getCause())
1006 ms vs 100 ms
allOf 在一个任务 100 ms 失败时,抛出异常的时刻
true / 照常跑完
cancel(true) 返回 true 后,底层任务的状态:没有被中断,600 ms 后仍然执行完

异常的包装

实测 · 本机执行输出
  supplyAsync 里抛出:handle 拿到的异常 = CompletionException(原因 IllegalStateException:boom)
  join() 抛出:CompletionException(原因 IllegalStateException:boom)
  get() 抛出:ExecutionException(原因 IllegalStateException:boom)
  completeExceptionally(原始异常) 的那个 future:handle 拿到的异常 = IllegalStateException:boom
  它的下游 thenApply 的 future:handle 拿到的异常 = CompletionException(原因 IllegalStateException:boom)
  completeExceptionally 的 future 上 join() 抛出:CompletionException(原因 IllegalStateException:boom)
  completeExceptionally 的 future 上 get() 抛出:ExecutionException(原因 IllegalStateException:boom)
  thenApply 里抛出:handle 拿到的异常 = CompletionException(原因 IllegalStateException:boom)
  exceptionally 在 supplyAsync 的 future 上收到的异常类型 = CompletionException(不是原始异常)
  exceptionally 在 completeExceptionally 的 future 上收到的异常类型 = IllegalStateException

沉默的异常

实测 · 本机执行输出
  runAsync 抛出异常、没有 join / handle:isCompletedExceptionally() = true,默认 UncaughtExceptionHandler 被调用 0 次,标准错误输出里没有任何堆栈(本进程到这里没有任何异常信息输出)
  thenRun 链上的后续阶段:isCompletedExceptionally() = true,没有被执行
  对照:ExecutorService.execute 里抛出异常,默认 UncaughtExceptionHandler 被调用 1 次(累计)

whenComplete / handle

实测 · 本机执行输出
  whenComplete(只观察)在失败的 future 上:结果仍然失败 = true
  whenComplete 的回调在成功的 future 上抛出 RuntimeException("B"):结果 = CompletionException(原因 RuntimeException:B)
  whenComplete 的回调在失败的 future 上又抛出 B:结果的异常 = CompletionException(原因 IllegalStateException:A),被抑制的异常数 = 0(原异常保留,B 被丢弃或抑制)
  handle 可以恢复:handle((v, e) -> e != null ? -1 : v) = -1;exceptionally(e -> -2) = -2
  exceptionallyCompose(JDK 12)用另一个 future 恢复:77
  handle 回调自己抛异常:CompletionException(原因 RuntimeException:C)

allOf 与 anyOf

实测 · 本机执行输出
  300 ms 时:fast 已失败 = true,allOf 是否已完成 = false
  all.join() 在 1006 ms 时抛出:CompletionException(原因 IllegalStateException:100 ms 时失败)
  anyOf 的结果在 110 ms 时是:CompletionException(原因 IllegalStateException:先失败的)(第一个完成的是失败的,第二个成功的结果被忽略)

取消与超时

实测 · 本机执行输出
  cancel(true) 返回 true,isCancelled() = true;底层任务照常跑完 = true,被中断 = false;下游 thenApply 的结果:CompletionException(原因 CancellationException:null)
  orTimeout(200 ms):在 211 ms 时 join() 抛出 CompletionException(原因 TimeoutException:null)
  700 ms 后底层任务仍然执行完了 = true
  completeOnTimeout("fallback", 200 ms):fallback

分步演示

CompletableFuture 异常传播
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12;默认执行器是 ForkJoinPool.commonPool;所有任务都是自写的 sleep 与抛异常的最小任务
    • 没有验证:JDK 8 / 11 / 17 上包装规则的差异(这里只在 JDK 21 上运行)、orTimeout 的内部定时线程、自定义 Executor 下异常的堆栈、Reactor / RxJava 的错误处理对比
    京ICP备2026067502号-1京公网安备11010502063487号