在日常开发中如果使用 CompletableFuture 的时候,如果出现异常,会怎么样呢,会只停止这个线程还是全部停止?会主动抛出异常吗,今天我通过一下案例来以结果来分析。
开发环境
# java 环境 openjdk 23.0.1 2024-10-15 OpenJDK Runtime Environment (build 23.0.1+11-39) OpenJDK 64-Bit Server VM (build 23.0.1+11-39, mixed mode, sharing)
两个 CompletableFuture 一个抛异常 一个不抛异常,执行结果是什么呢
/** * 不抛异常 */ public CompletableFuture<Void> normalCompletableFuture() { return CompletableFuture.runAsync(() -> { // 这里让线程休眠10秒 try { Thread.sleep(3000); } catch (InterruptedException e) { log.info("睡眠过程中出现异常 -> {}", e); } finally { log.info("普通线程执行完毕"); } }); } /** * 抛异常的线程 */ public CompletableFuture<Void> exceptionCompletableFuture() { return CompletableFuture.runAsync(() -> { throw new RuntimeException("runAsync -> 发生了一个大的异常"); }); }
然后可以使用 allOf 来组合两个线程
public void testRunRunAsync() { CompletableFuture<Void> voidCompletableFuture = CompletableFuture.allOf(this.exceptionCompletableFuture(), this.normalCompletableFuture()); voidCompletableFuture.join(); }
执行以后得到以下的执行结果
09:22:56.319 [ForkJoinPool.commonPool-worker-1] INFO org.flow.basic.threadpool.CompletableFutureRuntimeDemo -- 普通线程执行完毕 java.lang.RuntimeException: runAsync -> 发生了一个大的异常 at org.flow.basic.threadpool.CompletableFutureRuntimeDemo.lambda$exceptionCompletableFuture$1(CompletableFutureRuntimeDemo.java:38) at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1848) at java.base/java.util.concurrent.CompletableFuture$AsyncRun.exec(CompletableFuture.java:1840) at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:507) at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1458) at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:2034) at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:189)
我们可以看到虽然抛出异常了,但是并不会因为异常而让全部的执行的任务都停止,他会等待所有的任务都执行完毕,然后抛出异常。那如果不实用 join 使用其他的 api 是否会抛出异常呢? 把执行的代码略作改动我们再来执行看看效果
public void testRunRunAsync() { CompletableFuture<Void> voidCompletableFuture = CompletableFuture.allOf(this.exceptionCompletableFuture(), this.normalCompletableFuture()); while (true) { if (voidCompletableFuture.isDone()) { log.info("线程执行完毕"); break; } } }
执行结果
09:36:27.601 [ForkJoinPool.commonPool-worker-1] INFO org.flow.basic.threadpool.CompletableFutureRuntimeDemo -- 普通线程执行完毕 09:36:27.604 [main] INFO org.flow.basic.threadpool.CompletableFutureRuntimeDemo -- 线程执行完毕
这个可以看出我们并没有使用阻塞的方式,而是使用了 while 的死循环,这个会对 cpu 带来压力,不过我么可以看到他并不会报错并且两个任务全部执行完毕。
两个 CompletableFuture 一个抛异常 一个不抛异常,如果执行呢?
public void testRunRunAsync() { CompletableFuture<Void> voidCompletableFuture = CompletableFuture.allOf(this.exceptionCompletableFuture(), this.normalCompletableFuture()); voidCompletableFuture.exceptionally(e -> { log.error("发生异常,主动处理 -> {}", e.getMessage()); return null; }).join(); log.info("正常执行完毕"); }
打印结果
09:51:36.359 [ForkJoinPool.commonPool-worker-1] INFO org.flow.basic.threadpool.CompletableFutureRuntimeDemo -- 普通线程执行完毕 09:51:36.362 [ForkJoinPool.commonPool-worker-1] ERROR org.flow.basic.threadpool.CompletableFutureRuntimeDemo -- 发生异常,主动处理 -> java.lang.RuntimeException: runAsync -> 发生了一个大的异常 09:51:36.363 [main] INFO org.flow.basic.threadpool.CompletableFutureRuntimeDemo -- 正常执行完毕
这个我们可以看出如果主动处理了异常,但是没有使用 join 或者 isDone 等方式来判断,那么就会导致程序阻塞,并且不会抛出异常。
结论
CompletableFuture 的 allOf 的时候如果出现异常,不会主动抛出异常,但是会等待所有的任务都执行完毕,然后抛出异常。如果不用 join 等待,也可以通过 isDone 来判断是否执行完毕,这个并不会主动抛出异常,这个要自己来判断异常。但是会对 CPU 带来负载,这个时候可以根据实际情况来做衡量取舍。但是如果使用了 exceptionally 来处理异常,那么就会被异常处理拦截会根据自己的处理来做实际的异常行为