提问者:小点点

将默认的通用分叉/加入池与 CompletableFuture 一起使用来进行长时间的阻塞调用是否是一种不好的做法?


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

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

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

共2个答案

匿名用户

您不应该使用阻塞调用(以这种方式)的原因是,假定非阻塞作业,公共池并行性已经配置为利用现有的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());

在这里。在正式生产环境中,您将保留对执行器的引用,重用它,并最终对其调用关闭。对于执行器未被重用的用例,

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

就足够了,因为这样,线程将在作业完成后自动处置。

匿名用户

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

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

您可以选择使用CompletableFuture.supplyAsync(供应商