Использовать службу invokeAll или submit - java Executor

У меня есть сценарий, в котором я должен выполнить 5 потоков асинхронно для одного и того же вызываемого. Насколько я понимаю, есть два варианта:

1), используя submit (Callable)

ExecutorService executorService = Executors.newFixedThreadPool(5);
List<Future<String>> futures = new ArrayList<>();
for(Callable callableItem: myCallableList){
    futures.add(executorService.submit(callableItem));
}

2), используя invokeAll (Коллекции Callable)

ExecutorService executorService = Executors.newFixedThreadPool(5);
List<Future<String>> futures = executorService.invokeAll(myCallableList));
  • Каким должен быть предпочтительный способ?
  • Есть ли какой-либо недостаток или влияние на производительность в любом из них по сравнению с другим?

Ответ 1

Вариант 1: Вы отправляете задания в ExecutorService и вы не дожидаетесь завершения всех задач, которые были отправлены в ExecutorService

Вариант 2: вы ожидаете завершения всех задач, которые были отправлены в ExecutorService.

Каким должен быть предпочтительный способ?

В зависимости от требований приложения, любой из них является предпочтительным.

  1. Если вам не нужно ждать выполнения задачи submit() в ExecutorService, предпочитайте Option 1.
  2. Если вам нужно дождаться завершения всех задач, которые были отправлены в ExecutorService, предпочтите Option 2.

Есть ли какой-либо недостаток или влияние на производительность в любом из них по сравнению с другим?

Если ваше приложение требует Вариант 2, вам нужно дождаться завершения всех представленных задач в ExecutorService отличие от варианта 1. Производительность не является критерием для сравнения, поскольку оба они предназначены для двух разных целей.

И еще одна важная вещь: какой бы вариант вы ни FutureTask, FutureTask проглатывает исключения во время выполнения задачи. Ты должен быть осторожен. Взгляните на этот вопрос SE: Обработка исключений для ThreadPoolExecutor

С Java 8 у вас есть еще один вариант: ExecutorCompletionService

Служба CompletionService, которая использует предоставленный Исполнитель для выполнения задач. Этот класс организует отправку заданий по завершении, которые размещаются в очереди, доступной с помощью take. Класс является достаточно легким, чтобы быть пригодным для временного использования при обработке групп задач.

Посмотрите на связанный с этим вопрос SE: ExecutorCompletionService? Зачем нужен один, если у нас есть invokeAll?

Ответ 2

EDIT:

На самом деле существует разница между ними. По какой-то причине invokeAll() будет вызывать get() для каждого созданного future. Таким образом, он будет ждать завершения задач и поэтому может бросить InterruptedException (пока submit() ничего не выбрасывает).

Что Javadoc для метода invokeAll():

Выполняет заданные задачи, возвращая список фьючерсов, содержащих их статус и результаты , когда все завершено.

Итак, обе стратегии в основном делают то же самое, но если вы вызываете invokeAll(), вы будете заблокированы, пока не будут выполнены все задачи.


Ответ на оригинал (неполный):

Метод invokeAll() существует именно для таких ситуаций. Вы должны обязательно использовать его.

Вам действительно не нужно создавать экземпляр List, хотя:

ExecutorService executorService = Executors.newFixedThreadPool(5);
List<Future<String>> futures = executorService.invokeAll(myCallableList));

Этого должно быть достаточно, и он выглядит более чистым, чем первая альтернатива.

Ответ 3

Предположим, что у вас есть задача, результат которой зависит от количества исполняемых задач независимой. Но для первоначальной задачи вам нужно только ограниченное время. Как и его вызов API.

Так, например, у вас есть 100ms для выполнения задачи верхнего уровня, а также 10 зависимых задач. Для этого, если вы используете submit здесь, как будет выглядеть код.

List<Callable> tasks = []// assume contains sub tasks
List<Future> futures = [] 
for(Callable task: tasks) {
   futures.add(service.submit(task));
}

for(Future futute: futures) {
    future.get(100, TimeUnit.MILLISECONDS);
}

Итак, если каждый из подзадач занял более 50 мс для завершения вышеприведенного фрагмента кода, потребуется 50 мс. Но если каждый из подзадач занял 1000 мс для завершения вышеизложенного, он должен принимать 100 * 10 = 1000 мс или 1 с. Это затрудняет вычисление общего времени менее 100 мс для всех подзадач.

Метод invokeAll помогает нам в таком сценарии

List<Futures> futures = service.invokeall(tasks, 100, TimeUnit.MILLISECONDS)
for(Future future: futures) {
   if(!future.isCancelled()) {
       results.add(future.get());
   }
}

Таким образом, максимальное время, затрачиваемое на это, составляет 100 мс, даже если отдельные подзадачи заняли больше.