Нередко требуется запустить несколько задач одновременно и получить результат первой успешно выполненной задачи или объединить результаты всех выполненных задач. Для этого класс
ExecutorService предоставляет два метода: inVokeAll() и invokeAny()
Метод invokeAll() отправляет на выполнение все задачи из коллекции объектов Callable, блокирует их выполнение до завершения всех задач и возвращает список объектов Future,
которые представляют результаты всех задач, в том же порядке, в котором эти задачи были отправлены. Соответственно в соответствии с порядком можно совместить задачи и их результаты.
Недостатком данного метода в некоторых ситуациях может быть то, что он ждет завершения всех задач, даже если какие-то из них уже завершились с ошибкой.
Этот метод также имеет две версии:
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
Первая версия принимает коллекцию задач, которые надо выполнить, и возвращает список Future-объектов со статусом задач и их результатами.
Вторая версия также дополнительно принимает параметры тайм-аута, до истечения которого должны быть выполнены все задачи.
Рассмотрим на примере вычисления факториалов в следующей программе:
import java.util.concurrent.*;
import java.util.List;
import java.util.ArrayList;
record FactorialResult(int number, int factorial){}
class FactorialTask implements Callable<FactorialResult>{
private int number;
FactorialTask(int number) { this.number = number; }
public FactorialResult call() throws Exception{
if(number < 1) throw new Exception("Incorrect number: " + number + ". Number must be greater than 0");
int result = 1;
int n = number;
while(n > 0) result *= n--;
return new FactorialResult(number, result); // возвращаем результат задачи
};
}
class Program{
public static void main(String[] args) {
System.out.println("Main thread started...");
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
List<Callable<FactorialResult>> tasks = new ArrayList<Callable<FactorialResult>>();
// добавляем несколько задач
for(int i = -1; i < 4; i++) tasks.add(new FactorialTask(i));
// вызываем и ждем завершения всех задач
List<Future<FactorialResult>> results = executor.invokeAll(tasks);
// получаем результаты
for (Future<FactorialResult> result : results) {
if (result.state() == Future.State.SUCCESS){
FactorialResult factorialResult = result.resultNow();
System.out.printf("Factorial of %d: %d\n",
factorialResult.number(), factorialResult.factorial());
}
else{
var ex = result.exceptionNow();
System.out.println("Factorial exception: " + ex.getMessage());
}
}
} // здесь вызывается метод executor.close()
catch(Exception ex){
System.out.println(ex.getMessage());
}
System.out.println("Main thread finished...");
}
}
Здесь для представления результата задачи определен класс FactorialResult:
record FactorialResult(int number, int factorial){}
Зднсь поле number представляет число, для которого вычисляется факториал, а поле factorial - сам вычисленный факториал числа.
Для представления задачи определен класс FactorialTask, который реализует интерфейс Callable - в методе call вычисляет факториал числа, послученного через конструктор, и возвращает результа -
объект FactorialResult.
public FactorialResult call() throws Exception{
if(number < 1) throw new Exception("Incorrect number: " + number + ". Number must be greater than 0");
int result = 1;
int n = number;
while(n > 0) result *= n--;
return new FactorialResult(number, result); // возвращаем результат задачи
};
Допустим, нам надо вычислить факториалы от -1 до 4 (для теста выбираем ряд чисел заведо некорректных). Для этого создаем объект ExecutorService, который будет выполнять задачи на виртуальных потоках, и собственно список задач Callable:
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
List<Callable<FactorialResult>> tasks = new ArrayList<Callable<FactorialResult>>();
// добавляем несколько задач
for(int i = -1; i < 4; i++) tasks.add(new FactorialTask(i));
Затем вызываем все задачи с помощью метода executor.invokeAll() и ждем их завершения:
List<Future<FactorialResult>> results = executor.invokeAll(tasks);
На этой строке метод main приостанавливает выполнение и ждет завершения всех задач. И когда от них станут доступны результаты, проходим в цикле по все результатам и обрабатываем их:
for (Future<FactorialResult> result : results) {
if (result.state() == Future.State.SUCCESS){
FactorialResult factorialResult = result.resultNow();
System.out.printf("Factorial of %d: %d\n",
factorialResult.number(), factorialResult.factorial());
}
else{
var ex = result.exceptionNow();
System.out.println("Factorial exception: " + ex.getMessage());
}
}
Каждый результат представляет объект Future, у которого с помощью метода state() можно проверить состояние выполнения: если задача выполнена успешно, то используем
метод resultNow() для получения непосредственно самого результата в виде объекта FactorialResult. Если выполнение завершилось неудачно, то с помощью метода
exceptionNow() получаем возникшее исключение и выводим его сообщение на консоль.
Консольный вывод программы:
Main thread started... Factorial exception: Incorrect number: -1. Number must be greater than 0 Factorial exception: Incorrect number: 0. Number must be greater than 0 Factorial of 1: 1 Factorial of 2: 2 Factorial of 3: 6 Main thread finished...
Метод invokeAny() отправляет на выполнение все задачи из коллекции объектов Callable и блокирует выполнение до тех пор, пока одна из них не завершится успешно, вернув свой результат. Этот метод имеет две версии:
Метод invokeAny() отправляет на выполнение все задачи из коллекции объектов Callable и блокирует выполнение до тех пор, пока одна из них не завершится успешно, вернув свой результат. Этот метод имеет две версии:
<T> T invokeAny(Collection<? extends Callable<T>> tasks) <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
Первая версия метода принимает коллекцию задач, из которых надо завершить одну. Результатом метода является результат первой завершенной задачи.
Вторая версия дополнительно принимает параметры тайм-аута - времени, до истечения которого должна быть выполнена одна из задач.
При этом все незавершенные задачи отменяются. Если ни одна задача не была выполнена успешно, генерируется исключение ExecutionException.
Изменим предыдущий код - пусть теперь хотя бы одна из задач по вычислению факториала будет выполнена:
import java.util.concurrent.*;
import java.util.List;
import java.util.ArrayList;
record FactorialResult(int number, int factorial){}
class FactorialTask implements Callable<FactorialResult>{
private int number;
FactorialTask(int number) { this.number = number; }
public FactorialResult call() throws Exception{
if(number < 1) throw new Exception("Incorrect number: " + number + ". Number must be greater than 0");
int result = 1;
int n = number;
while(n > 0) result *= n--;
return new FactorialResult(number, result); // возвращаем результат задачи
};
}
class Program{
public static void main(String[] args) {
System.out.println("Main thread started...");
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
List<Callable<FactorialResult>> tasks = new ArrayList<Callable<FactorialResult>>();
// добавляем несколько задач
for(int i = -1; i < 4; i++) tasks.add(new FactorialTask(i));
// вызываем задачи и ждем, пока хотя бы одна из них не завершенится
FactorialResult factorialResult = executor.invokeAny(tasks);
System.out.printf("Factorial of %d: %d\n",
factorialResult.number(), factorialResult.factorial());
}
catch(Exception ex){
System.out.println(ex.getMessage());
}
System.out.println("Main thread finished...");
}
}
Большая часть логики здесь та же, что и в предыдущем примере. Отличие только в том что ждем выполнения одной задачи с помощью invokeAny():
FactorialResult factorialResult = executor.invokeAny(tasks);
System.out.printf("Factorial of %d: %d\n", factorialResult.number(), factorialResult.factorial());
И в данном случае вывод может быть следующим (консольный вывод не детерминирован):
Main thread started... Factorial of 3: 6 Main thread finished...
Если при выполнении задачи возникнет ошибка, то будет сгенерировано исключение, которое будет обработано в блоке catch:
Main thread started... java.lang.Exception: Incorrect number: -1. Number must be greater than 0 Main thread finished...