Вопрос-ответ

How to wait for all threads to finish, using ExecutorService?

Как дождаться завершения всех потоков, используя ExecutorService?

Мне нужно выполнить некоторое количество задач по 4 одновременно, что-то вроде этого:

ExecutorService taskExecutor = Executors.newFixedThreadPool(4);
while(...) {
taskExecutor.execute(new MyTask());
}
//...wait for completion somehow

Как я могу получить уведомление после завершения всех потоков? На данный момент я не могу придумать ничего лучше, чем установить некоторый глобальный счетчик задач и уменьшать его в конце каждой задачи, затем отслеживать в бесконечном цикле, чтобы этот счетчик стал равным 0; или получить список фьючерсов и в бесконечном цикле отслеживать, выполняется ли для всех них. Какие решения лучше, не включающие бесконечные циклы?

Спасибо.

Переведено автоматически
Ответ 1

В основном, ExecutorService вы вызываете shutdown(), а затем awaitTermination():

ExecutorService taskExecutor = Executors.newFixedThreadPool(4);
while(...) {
taskExecutor.execute(new MyTask());
}
taskExecutor.shutdown();
try {
taskExecutor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
...
}
Ответ 2

Используйте обратный отсчет:

CountDownLatch latch = new CountDownLatch(totalNumberOfTasks);
ExecutorService taskExecutor = Executors.newFixedThreadPool(4);
while(...) {
taskExecutor.execute(new MyTask());
}

try {
latch.await();
} catch (InterruptedException E) {
// handle
}

и в рамках вашей задачи (вложите в try / finally)

latch.countDown();
Ответ 3

ExecutorService.invokeAll() сделает это за вас.

ExecutorService taskExecutor = Executors.newFixedThreadPool(4);
List<Callable<?>> tasks; // your tasks
// invokeAll() returns when all tasks are complete
List<Future<?>> futures = taskExecutor.invokeAll(tasks);
Ответ 4

Вы также можете использовать списки фьючерсов:

List<Future> futures = new ArrayList<Future>();
// now add to it:
futures.add(executorInstance.submit(new Callable<Void>() {
public Void call() throws IOException {
// do something
return null;
}
}));

затем, когда вы хотите присоединиться ко всем из них, это, по сути, эквивалентно присоединению к каждому (с дополнительным преимуществом, заключающимся в том, что оно повторно вызывает исключения из дочерних потоков в основной):

for(Future f: this.futures) { f.get(); }

По сути, хитрость заключается в вызове .get() для каждого будущего по очереди, вместо бесконечного цикла вызова isDone() для (всех или каждого). Таким образом, вы гарантированно "перейдете" через этот блок, как только завершится последний поток. Предостережение заключается в том, что, поскольку вызов .get() повторно вызывает исключения, если один из потоков завершается, вы должны использовать это, возможно, до того, как другие потоки завершатся до завершения [чтобы избежать этого, вы могли бы добавить catch ExecutionException вокруг вызова get]. Другое предостережение заключается в том, что он сохраняет ссылки на все потоки, поэтому, если у них есть локальные переменные потока, они не будут собраны до тех пор, пока вы не пройдете этот блок (хотя вы могли бы обойти это, если это стало проблемой, удалив Future из ArrayList). Если вы хотите знать, какое будущее "завершается первым", вы могли бы использовать что-то вроде https://javalang.ru/a/31885029/32453

java multithreading concurrency