简体   繁体   English

CompletableFuture:运行期货列表,等待结果和处理异常的正确方法

[英]CompletableFuture: proper way to run a list of futures, wait for result and handle exception

I have a legacy code which have dozen database calls to populate a report, it takes noticeable amount of time which I try to reduce using CompletableFuture . 我有一个遗留代码,它有十几个数据库调用来填充报告,我尝试使用CompletableFuture减少需要花费大量时间。

I have some doubts that I do things correctly and not overuse this technology. 我有一些疑问,我正确地做事,而不是过度使用这项技术。

My code now looks like this: 我的代码现在看起来像这样:

  1. Start asynchronous population of document sections with many database calls inside each methods 在每个方法中启动具有许多数据库调用的文档部分的异步填充

     CompletableFuture section1Future = CompletableFuture.supplyAsync(() -> populateSection1(arguments)); CompletableFuture section2Future = CompletableFuture.supplyAsync(() -> populateSection2(arguments)); ... CompletableFuture section1oFuture = CompletableFuture.supplyAsync(() -> populateSection10(arguments)); 
  2. Then I'm arranging futures in specific order in arrayList and joining all of them to make sure that my code will run further only when all futures are finished. 然后我将在arrayList按特定顺序排列期货并加入所有期货,以确保我的代码只有在所有期货完成后才会进一步运行。

     List<CompletableFuture> futures = Arrays.asList( section1Future, section2Future, ... section10Future); List<Object> futureResults = futures.stream() .map(CompletableFuture::join) .collect(Collectors.toList()); 
  3. Then I'm populating PDF document itself with its pieces 然后我用它的片段填充PDF文档本身

     Optional.ofNullable((PdfPTable) futureResults.get(0)).ifPresent(el -> populatePdfElement(document, el)); Optional.ofNullable((PdfPTable) futureResults.get(1)).ifPresent(el -> populatePdfElement(document, el)); ... Optional.ofNullable((PdfPTable) futureResults.get(10)).ifPresent(el -> populatePdfElement(document, el)); 

    return document 退货文件

My concerns are: 我担心的是:

1) Is it okay to create and instantiate many Completable Futures in such way? 1)以这种方式创建和实例化许多可完成期货是否可以? Order them in required sequence in arrayList , join them to make sure that they are all finished, and then get result by casting them into specific object? arrayList按需要的顺序对它们进行arrayList ,加入它们以确保它们都已完成,然后通过将它们转换为特定对象来获得结果?

2) Is it okay to run without specifying an executor service but to rely on common ForkJoinPool ? 2)没有指定执行程序服务但是依赖于常见的ForkJoinPool吗? However this code runs in web container, so probably in order to use JTA I need to use container provided thread pool executor via JNDI? 但是这个代码在web容器中运行,所以为了使用JTA,我需要通过JNDI使用容器提供的线程池执行器吗?

3) If this code is surrounded in try-catch I should be able to catch CompletionException in main thread, right? 3)如果这个代码被try-catch包围,我应该能够在主线程中捕获CompletionException ,对吧? Or In order to do that I should declare each features like following: 或者为了做到这一点,我应该声明每个功能如下:

CompletableFuture.supplyAsync(() -> populateSection1(arguments))
    .exceptionally (ex -> {
                    throw new RuntimeException(ex.getCause());
        });

4) Is it possible to overuse CompletableFutures so they become a performance bottleneck itself? 4)是否有可能过度使用CompletableFutures,因此它们本身就成为性能瓶颈? Like many futures waits one executor to start running? 像许多期货等待一个执行者开始运行? How to avoid that? 怎么避免呢? Use container provided executor service? 使用容器提供的执行器服务? If yes, could someone please point me to some best practice on how to correctly configure executor service taking to account processors and memory amount? 如果是,有人可以请我指出一些关于如何正确配置执行者服务以考虑处理器和内存量的最佳实践吗?

5) A memory impact. 5)内存影响。 I read in parallel thread that there can be a problem with OOME as many object are created and garbage collected. 我在并行线程中读到OOME可能存在问题,因为创建了很多对象并收集了垃圾。 Is there a best practice on how to calculate correct amount of memory required for application? 有关如何计算应用程序所需的正确内存量的最佳实践吗?

The approach is not wrong in general, but there are things to improve. 这种方法一般没有错,但有些事情需要改进。

Most notably, you should not use raw types , like CompletableFuture . 最值得注意的是,您不应该使用原始类型 ,例如CompletableFuture

When populateSection… returns a PdfPTable , you should use use CompletableFuture<PdfPTable> consistently throughout the code. populateSection…返回一个PdfPTable ,你应该在整个代码中使用CompletableFuture<PdfPTable>

Ie

CompletableFuture<PdfPTable> section1Future = CompletableFuture.supplyAsync(()  -> populateSection1(arguments));
CompletableFuture<PdfPTable> section2Future = CompletableFuture.supplyAsync(()  -> populateSection2(arguments));
    ...
CompletableFuture<PdfPTable> section10Future = CompletableFuture.supplyAsync(() -> populateSection10(arguments));

even if these methods do not declare the return type you are assuming to be always returned at runtime, you should insert the type cast at this early stage: 即使这些方法没有声明你假设总是在运行时返回的返回类型,你应该在这个早期阶段插入类型转换:

CompletableFuture<PdfPTable> section1Future = CompletableFuture.supplyAsync(()  -> (PdfPTable)populateSection1(arguments));
CompletableFuture<PdfPTable> section2Future = CompletableFuture.supplyAsync(()  -> (PdfPTable)populateSection2(arguments));
    ...
CompletableFuture<PdfPTable> section10Future = CompletableFuture.supplyAsync(() -> (PdfPTable)populateSection10(arguments));

Then, you can use 然后,你可以使用

Stream.of(section1Future, section2Future, ..., section10Future)
    .map(CompletableFuture::join)
    .filter(Objects::nonNull)
    .forEachOrdered(el -> populatePdfElement(document, el));

By not using raw types, you already get the desired result type and you can do the 3rd step's operations, ie filtering and performing the final action, right in this stream operation. 通过不使用原始类型,您已经获得了所需的结果类型,您可以在此流操作中执行第3步操作,即过滤和执行最终操作。

If you still need the list, you may use 如果您仍然需要该列表,您可以使用

List<PdfPTable> results = Stream.of(section1Future, section2Future, ..., section10Future)
    .map(CompletableFuture::join)
    .filter(Objects::nonNull)
    .collect(Collectors.toList());

results.forEach(el -> populatePdfElement(document, el));

That said, the parallelism depends on the thread pool used for the operation (specified to supplyAsync ). 也就是说,并行性取决于用于操作的线程池(指定为supplyAsync )。 When you don't specify an executor, you get the default Fork/Join pool used by parallel streams, so in this specific case, you get the same result much simpler as 如果未指定执行程序,则会获得并行流使用的默认Fork / Join池,因此在这种特定情况下,您可以获得与

List<PdfPTable> results = Stream.<Supplier<PdfPTable>>.of(
    ()  -> populateSection1(arguments),
    ()  -> populateSection2(arguments));
    ...
    () -> populateSection10(arguments)))
    .parallel()
    .map(Supplier::get)
    .filter(Objects::nonNull)
    .forEachOrdered(el -> populatePdfElement(document, el));

or 要么

List<PdfPTable> results = Stream.<Supplier<PdfPTable>>.of(
    ()  -> populateSection1(arguments),
    ()  -> populateSection2(arguments));
    ...
    () -> populateSection10(arguments)))
    .parallel()
    .map(Supplier::get)
    .filter(Objects::nonNull)
    .collect(Collectors.toList());

results.forEach(el -> populatePdfElement(document, el));

While both variants ensure that populatePdfElement will be called in the right order and one at a time, only the latter will perform all calls from the initiating thread. 虽然两种变体都确保以正确的顺序调用populatePdfElement ,并且一次调用一个,但只有后者才会执行来自启动线程的所有调用。

Regarding exception handling, you'll get any exception thrown by a supplier wrapped in a CompletionException when you call CompletableFuture::join . 关于异常处理,当您调用CompletableFuture::join时,您将获得由CompletionException包装的供应商抛出的任何异常。 Chaining something like .exceptionally (ex -> { throw new RuntimeException(ex.getCause()); }); 链接类似.exceptionally (ex -> { throw new RuntimeException(ex.getCause()); }); makes no sense, the new RuntimeException will also be wrapped in a CompletionException when you call CompletableFuture::join . 没有意义,当您调用CompletableFuture::join时,新的RuntimeException也将被包装在CompletionException

In the Stream variant, you'll get the exception without a wrapper. 在Stream变体中,您将获得没有包装器的异常。 Since Supplier does not allow checked exceptions, only subtypes of RuntimeException or Error are possible. 由于Supplier不允许检查异常,因此只能运行RuntimeExceptionError子类型。

The other questions are too broad for the Q&A. 其他问题对于问答来说过于宽泛。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 等待列表的正确方法<completablefuture<void> &gt; 表示所有操作都已完成</completablefuture<void> - Proper way to wait for List<CompletableFuture<Void>> to indicate all operations have finished 正确的方法来处理CompletableFuture.allof中的超时并记录超时的期货 - correct way to handle timeout in CompletableFuture.allof and logging the timed out futures 传递给 CompletableFuture.allOf() 的所有期货都会运行吗? - Will all futures passed to CompletableFuture.allOf() run? 解决/处理列表的最佳方式<CompletableFuture<List<Object> &gt;&gt; 在 Java 8 中 - Optimal way to resolve/handle List<CompletableFuture<List<Object>>> in Java 8 在回调内处理异常的正确方法是什么? - What is the proper way to handle exception inside callback? CompletableFuture.allOf 抛出异常时取消其他期货 - CompletableFuture.allOf cancel other futures when one throws exception 如何等待期货清单完成 - How to wait for a list of Futures to complete 处理项目“清单”的正确方法 - Proper way to handle “check list” of items 让 CompletableFuture 异常()处理 supplyAsync() 异常 - Letting CompletableFuture exceptionally() handle a supplyAsync() Exception 如何处理 CompletableFuture get() 方法的异常 - How to handle exception of CompletableFuture get() method
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM