【问题标题】:Execute a for loop in parallel using CompletableFuture in Java and log the execution在 Java 中使用 CompletableFuture 并行执行 for 循环并记录执行
【发布时间】:2018-11-18 11:53:05
【问题描述】:

我有一个 for 循环,我正在尝试使用 CompletableFuture 对其进行并行化。

for (int i = 0; i < 10000; i++) {
    doSomething();
    doSomethingElse();
}

我现在拥有的是:

for (int i = 0; i < 10000; i++) {
    CompletableFuture.runAsync(() -> doSomething());
    CompletableFuture.runAsync(() -> doSomethingElse());
}

我想这可以达到目的,但需要在所有处理的开始和结束之前打印日志。如果我这样做:

log("Started doing things");
for (int i = 0; i < 10000; i++) {
    CompletableFuture.runAsync(() -> doSomething());
    CompletableFuture.runAsync(() -> doSomethingElse());
}
log("Ended doing things");

这是否保证在所有 for 循环结束后将打印第二条日志语句,因为它是在单独的线程中执行的?如果没有,有没有办法在不阻塞主线程的情况下做到这一点?

【问题讨论】:

  • 为什么要使用CompletableFuture? (您似乎不再在循环之外使用这些对象,因此无需使用CompletableFutures。)
  • 为了保证循环在不阻塞主线程的情况下执行完毕
  • 因此您不需要CompletableFuture。 Java 中还有其他更适合此目的的机制。

标签: java multithreading future executorservice completable-future


【解决方案1】:

我想CompletableFuture 对您的需求来说是错误的概念。如果要并行执行任意数量的类似任务,最简单的方法是在ExecutionService 上使用invokeAll(...) 方法:

// First, create a list with all tasks you want to execute in parallel
List<Callable<?>> tasks = new ArrayList<>(10000);
for (int i = 0; i < 10000; ++i) {
    // we need to create Callables, so if your doSomething method returns void, we have to convert it to a Callable using helper method from class Executors
    tasks.add(Executors.callable(this::doSomething));
}

// Then, create an executor service that can execute these tasks
// There are different executors you can choose from, I take one that has a fixed pool of threads
ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

// Last but not least, call invokeAll to execute all tasks and wait for them to complete
executorService.invokeAll(tasks);

// This method will be called when all tasks have been completed successfully:
System.out.println("done");

【讨论】:

  • invokeAll() 是阻塞调用吗?我不想阻塞主线程。
  • 是的。但是您写道,您希望在所有任务完成后调用第二条日志语句。这正是阻塞的含义。
  • 也许我可以在不同的线程中做到这一点?我使用 CompletableFuture 因为它有 thenRun() 不会阻塞主线程。哪种方式更好?
  • 所以你不关心运行你的第二条日志语句的线程?
  • 不,我只是希望它在 for 循环之后立即执行而不阻塞主线程。
【解决方案2】:

您必须收集所有 CompletableFuture 并等待它们完成:

log("Started doing things");
List<CompletableFuture> futures = new ArrayList();
for (int i = 0; i < 10000; i++) {
    futures.add(CompletableFuture.runAsync(() -> doSomething()));
    futures.add(CompletableFuture.runAsync(() -> doSomethingElse()));
}
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
                 .thenRunAsync(() -> log("Ended doing things"));

或者当你使用 ExecutorService 时:

CompletableFuture.runAsync(() -> {
    try {
        executorService.invokeAll(tasks);
    } catch (InterruptedException) {
        e.printStackTrace();
    }
    log("Ended doing things");
});

【讨论】:

  • futures.foreach(CompletableFuture::join) 阻塞了吗?我不想阻塞主线程。
  • 是的,但是如果你想让主线程继续运行,你可以在另一个线程中执行连接操作。
  • 这很整洁。谢谢!
  • 删除使用ExecutorService 的第二个示例,因为它不能以这种方式工作(invokeAll 是阻塞调用)。
  • 如果你想使用显式的ExecutorService,你可以将它指定为CompletableFuture.runAsync(...)的第二个参数
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-07
  • 2012-07-22
  • 2020-07-17
相关资源
最近更新 更多