当前位置:Java -> Java中嵌套CompletableFuture的问题
CompletableFuture
在 Java 8 中被引入,用于以异步方式执行任务。这可以帮助防止主线程等待完成不需要主线程执行的特定代码块的执行。CompletableFuture
在执行时使用来自 ForkJoinPool.commonPool()
的线程,如果没有作为参数传递的自定义执行器。
如果并行度小于两个,则 CompletableFuture
为每个提交的异步任务创建一个新线程。ForkJoinPool.commonPool()
的线程数量基于设置的 java.util.concurrent.ForkJoinPool.common.parallelism 属性
。
java.util.concurrent.ForkJoinPool.common.parallelism
默认设置为与运行 JVM 的系统中可用的核心数相等的值。例如,如果我们在一个 4 个 CPU 的机器上运行 JVM,则 java.util.concurrent.ForkJoinPool.common.parallelism
的值将为 4,ForkJoinPool.commonPool()
也将创建 4 个线程。我们可以通过在启动时传递此参数来增加此 java.util.concurrent.ForkJoinPool.common.parallelism
的值 —Djava.util.concurrent.ForkJoinPool.common.parallelism=<count>
。此属性不能在运行时修改,必须在 JVM 启动时作为命令行参数设置。
当应用程序收到大量请求执行时,代码中的嵌套 CompletableFuture
会导致问题。例如,在代码中,有一个 CompletableFuture1
,它又包含另一个 CompletableFuture2
。 CompletableFuture
1 和 2 都需要分配单独的线程来执行分配的任务。在这种情况下,它需要两个线程从 ForkJoinPool.commonPool()
中执行任务。如果在执行此代码时应用程序中没有其他请求,且并行性为 4,则它将成功运行。它将获得在 ForkJoinPool.commonPool()
中可用的线程,并成功执行任务。如果应用程序收到更多请求,则需要更多线程来执行这些嵌套的 CompletableFuture
任务。 ForkJoinPool.commonPool()
可能耗尽线程,应用程序可能会陷入一种无法处理请求的悬挂状态。
例如,如果我们在一个具有 4 个 CPU 的机器上运行应用程序,则默认情况下 ForkJoinPool.commonPool()
中将有 4 个线程。如果应用程序同时接收 4 个请求,则它将进入 CompletableFuture1
,并尝试执行 CompletableFuture2
,其中它将等待线程,因为所有 ForkJoinPool.commonPool()
线程已经忙于执行 CompletableFuture1
。这将使应用程序进入一种悬挂状态,并且不会继续处理当前的 4 个请求以及即将到达此嵌套 CompletableFuture
代码的请求。它还可能阻止使用 CompletableFuture
内部的其他请求进行处理。
为了解决这种情况,可以将 java.util.concurrent.ForkJoinPool.common.parallelism
参数增加到更高的值作为短期解决方法。这可能会解决上述博客中提到的情况,但仅当同时收到的请求数量等于或多于设置的 java.util.concurrent.ForkJoinPool.common.parallelism
参数值时才会发生。而且,有时,即使同时接收到的请求数量有微秒级的变化,或者 CompletableFuture
代码块以更快的方式执行,问题也可能不会发生。作为永久解决此情况的方法,最好避免像这样具有嵌套异步任务,因为它使用更多线程的开销以及可能陷入悬挂状态。
本文链接: Java中嵌套CompletableFuture的问题