使用带有 CompletableFuture 的默认通用 fork/join 池进行长时间阻塞调用是不好的做法吗?

Posted

技术标签:

【中文标题】使用带有 CompletableFuture 的默认通用 fork/join 池进行长时间阻塞调用是不好的做法吗?【英文标题】:Is it bad practice to use default common fork/join pool with CompletableFuture for doing long blocking calls? 【发布时间】:2018-01-28 08:14:00 【问题描述】:

假设我有一个 CompletableFuture,它包装了一个阻塞调用,比如使用 JDBC 查询后端。在这种情况下,由于我没有将任何执行程序服务作为参数传递给 CompletableFuture.supplyAsync(),因此通过后端获取资源的实际阻塞工作应该由公共 Fork/Join 池中的线程完成。 bad practice 不是让来自普通 FJpool 的线程进行阻塞调用吗? 我的优势是我的主线程没有阻塞,因为我委托阻塞调用异步运行.检查 abt JDBC 调用是否阻塞 here 。如果这个推论是真的,为什么可以选择使用默认的通用 FJpool 和 CompletableFuture?

CompletableFuture<List<String>> fetchUnicorns  = 
    CompletableFuture.supplyAsync(() -> 
        return unicornService.getUnicorns();
    );

fetchUnicorns.thenAccept(/**Do something with the result*/);

【问题讨论】:

CompletableFuture 作为 API 有许多设计缺陷,这就是其中之一。是的,在任何线程池(包括 FJ 池)中混合阻塞和非阻塞任务是一种不好的做法。 【参考方案1】:

您不应使用阻塞调用(以这种方式)的原因是,公共池并行性已配置为利用现有 CPU 内核,假设非阻塞作业。阻塞的线程会降低使用同一池的其他任务的并行度。

但是有官方的解决方法:

class BlockingGetUnicorns implements ForkJoinPool.ManagedBlocker 
    List<String> unicorns;
    public boolean block() 
        unicorns = unicornService.getUnicorns();
        return true;
    
    public boolean isReleasable()  return false; 

CompletableFuture<List<String>> fetchUnicorns  = 
    CompletableFuture.supplyAsync(() -> 
        BlockingGetUnicorns getThem = new BlockingGetUnicorns();
        try 
            ForkJoinPool.managedBlock(getThem);
         catch (InterruptedException ex) 
            throw new AssertionError();
        
        return getThem.unicorns;
    );

ForkJoinPool.ManagedBlocker 是潜在阻塞操作的抽象,允许 Fork/Join 池在识别到工作线程即将被阻塞时创建补偿线程。

应该很明显,使用起来要容易得多

CompletableFuture<List<String>> fetchUnicorns  = 
    CompletableFuture.supplyAsync(() -> unicornService.getUnicorns(),
                                  Executors.newSingleThreadExecutor());

这里。在生产环境中,您将保留对执行程序的引用,重用它并最终在其上调用shutDown。对于执行器不被重用的用例,

CompletableFuture<List<String>> fetchUnicorns  = 
    CompletableFuture.supplyAsync(() -> unicornService.getUnicorns(),
                                  r -> new Thread(r).start());

这样就足够了,线程将在作业完成后自动释放。

【讨论】:

感谢您提供有关 ForkJoinPool.ManagedBlocker 的信息。有帮助! 这个答案真的很有帮助!谢谢。【参考方案2】:

如果这个推论是真的,为什么可以选择使用默认的通用 FJpool 和 CompletableFuture?

因为并非所有工作都在阻塞。

您可以选择使用CompletableFuture.supplyAsync(Supplier&lt;U&gt;, Executor) 在自定义执行程序上安排您的阻止工作

【讨论】:

以上是关于使用带有 CompletableFuture 的默认通用 fork/join 池进行长时间阻塞调用是不好的做法吗?的主要内容,如果未能解决你的问题,请参考以下文章

使用 CompletableFuture 检查异常 [重复]

J:牛顿方法的默示副词

CompletableFuture的使用例子

CompletableFuture的使用例子

CompletableFuture进阶篇-外卖商家端API的异步化

如何将异步CompletableFuture与完成的CompletableFuture结合起来?