CompletableFuture:几项任务

时间:2018-04-15 07:37:26

标签: java java-8 runnable java-threads completable-future

如何使用5个CompletableFutures异步执行20个Runnable任务(或1个任务20次)?

这就是我所拥有的:

Runnable task = () -> {
        long startTime = System.currentTimeMillis();
        Random random = new Random();

        while (System.currentTimeMillis() - startTime < 3000) {
            DoubleStream.generate(() -> random.nextDouble())
                    .limit(random.nextInt(100))
                    .map(n -> Math.cos(n))
                    .sum();
        }
        System.out.println("Done");
    };

    for (int i = 0; i < 4; i++) {
        CompletableFuture<Void> future1 = CompletableFuture.runAsync(task);
        CompletableFuture<Void> future2 = CompletableFuture.runAsync(task);
        CompletableFuture<Void> future3 = CompletableFuture.runAsync(task);
        CompletableFuture<Void> future4 = CompletableFuture.runAsync(task);
        CompletableFuture<Void> future5 = CompletableFuture.runAsync(task);
        future1.get();
        future2.get();
        future3.get();
        future4.get();
        future5.get();
    }

如果我执行此代码,我可以看到它只是异步运行3 future.get(): 3,然后2在1 for for()迭代期间离开

所以,我想尽可能异步地完成所有20项任务

3 个答案:

答案 0 :(得分:1)

您可以使用allOf同时运行多个任务。首先,我创建了5个任务的组合(与您的问题相同),但后来我添加了10个(并且仅加载了两次)并且获得了一半的执行时间。

for (int i = 0; i < 2; i++) {
   CompletableFuture<Void> future1 = CompletableFuture.runAsync(task);
   CompletableFuture<Void> future2 = CompletableFuture.runAsync(task);
  // and so on until ten  
   CompletableFuture<Void> future10 = CompletableFuture.runAsync(task);

   CompletableFuture<Void> combined = CompletableFuture.allOf(future1, future2, future3, future4, future5, future6, future7, future8, future9, future10);

   combined.get();
}

答案 1 :(得分:1)

CompletableFuture的默认执行程序是ForkJoinPool的公共池,它具有与CPU核心数减1相匹配的默认目标并行度。因此,如果您有四个核心,则最多可以异步执行三个作业。由于您每5个作业强制等待完成,因此您将获得三个并行执行,然后在每个循环迭代中执行两个并行执行。

如果您想获得特定执行策略,例如您选择的并行性,最好的方法是指定正确配置的执行程序。然后,您应该让执行程序管理并行性而不是循环等待。

ExecutorService pool = Executors.newFixedThreadPool(5);

for (int i = 0; i < 20; i++) {
    CompletableFuture.runAsync(task, pool);
}
pool.shutdown();
pool.awaitTermination(1, TimeUnit.DAYS); // wait for the completion of all tasks

这允许五个并行作业,但是会让五个线程中的每个线程在一个完成后立即获取新作业,而不是等待下一个循环迭代。

但是当你说

  

所以,我想尽可能异步地完成所有20项任务

目前尚不清楚为什么要在安排五个工作后执行等待。最大并行度可以通过

实现
ExecutorService pool = Executors.newCachedThreadPool();
for (int i = 0; i < 20; i++) {
    CompletableFuture.runAsync(task, pool);
}
pool.shutdown();
pool.awaitTermination(1, TimeUnit.DAYS); // wait for the completion of all tasks

这可能会产生与作业一样多的线程,除非一个作业在所有作业完成之前完成,因为在这种情况下,工作线程可能会选择一个新作业。

但是这个逻辑根本不需要CompletableFuture。您也可以使用:

ExecutorService pool = Executors.newCachedThreadPool();
// schedule 20 jobs and return when all completed
pool.invokeAll(Collections.nCopies(20, Executors.callable(task)));
pool.shutdown();

但是当你的工作不涉及I / O或任何其他类型的等待时。释放CPU,创建比CPU核心更多的线程毫无意义。配置为处理器数量的池最好。

ExecutorService pool = Executors.newWorkStealingPool(
    Runtime.getRuntime().availableProcessors());
// schedule 20 jobs at return when all completed
pool.invokeAll(Collections.nCopies(20, Executors.callable(task)));
pool.shutdown();

在您的特殊情况下,由于您的作业使用系统时间在拥有比核心更多的线程时运行速度更快,但实际执行的工作量更少,因此可能会运行得更慢。但对于普通的计算任务,这将提高性能。

答案 2 :(得分:0)

将以下系统属性设置为您希望公共fork连接池使用的线程数:

java.util.concurrent.ForkJoinPool.common.parallelism 

请参阅ForkJoinPool

原因是您在构建可完成的期货时没有指定自己的fork连接池,因此它隐式使用

ForkJoinPool.commonPool()

请参阅CompletableFurure