Будущее или интерфейс Future вместе с Callable
Привет дорогой читатель! Эта статья рассчитана больше на новичков, нежели профессионалов потому что обсуждаться в этом посте будет примитивная вещь.
Все наверное из нас знают или хотябы слышали о многопоточности в Java. Но не многие умеют правильно использовать ее. И в итоге пишут однопоточные программы чтобы не заморачиваться с многопоточностью и синхронизацией потоков.
Сегодня мы поговорим о том, как делать параллельные вычисления и получать результат этих вычислений. В сегодняшнем уроке мы посчитаем несколько величин:
- Факториал числа (в отдельном потоке) циклом
- Гипотенузу обычная математическая операция
- НОД алгоритмом Евклида
давайте приступим.
Наверняка вы часто использовали интерфейс Runnable который позволяет сделать новую задачу для исполнения в отдельном потоке. Но не все слышали об интерфейсе Callable<T> это тоже самое что и Runnable только умеет возвращать результат из потока как будущее (Future<V>) я считаю что это имя более чем логично, так как результат вернется в будущем, а не прямо сейчас, очень сильно напоминает промисы в JavaScript.
Исполнять нашу программу мы будем с помощью Executor'ов. Если вы еще не слышали о них, то советую почитать.
Давайте напишем первый таск который будет возвращать результат из отдельного потока. Это будет класс который вычисляет факториал числа.
class Factorial implements Callable<Long> {
private final int digit;
Factorial(int digit) {
this.digit = digit;
}
@Override
public Long call() throws Exception {
long factorial = 1;
for (int i = 2; i < digit; i++) {
factorial *= i;
}
return factorial;
}
}
Тут мы вычисляем факториал в цикле и возвращает результат типа Long. Самый примитивный алгоритм. Идем дальше. Теперь вычислим гипотенузу.
class Hypotenuse implements Callable<Double> {
private final int cathetusA;
private final int cathetusB;
Hypotenuse(int cathetusA, int cathetusB) {
this.cathetusA = cathetusA;
this.cathetusB = cathetusB;
}
@Override
public Double call() throws Exception {
double hypotenuse = Math.pow(cathetusA, 2) + Math.pow(cathetusB, 2);
return Math.sqrt(hypotenuse);
}
}
Тут тоже все просто, обычная теорема Пифагора. Возвращаем результат типа double. Идем дальше и вычисляем НОД.
class NOD implements Callable<Integer> {
private int a;
private int b;
NOD(int a, int b) {
this.a = a;
this.b = b;
}
@Override
public Integer call() throws Exception {
while (b != 0) {
int tmp = a % b;
a = b;
b = tmp;
}
return a;
}
}
А этот поток вычисляет НОД. Тут тоже все просто. Единственное замечание. Наверное лучше не менять внутринние переменные класса в цикле. Нужно было сделать их локальными переменными метода call().
теперь самое интересное. Давайте запустим вычисление.
ExecutorService executorService = Executors.newFixedThreadPool(3);
Future<Long> factorial = executorService.submit(new Factorial(factIn));
Future<Double> hypotenuse = executorService.submit(new Hypotenuse(cath1, cath2));
Future<Integer> nod = executorService.submit(new NOD(nod1, nod2));
System.out.printf(
"Факториал: %20d%nГипотенуза: %21.2f%nНОД: %25d%nСумма всех значений: %13.2f",
fact.get(), hypotenuse.get(), nod.get(), (fact.get() + hypotenuse.get() + nod.get())
);
executorService.shutdown();
Самое интересное тут. Создаем FixedThreadPool из трех потоков. Далее используем наш ExecutorService чтобы параллельно вычислить наши величины и выводим результат вычисления на консоль. После чего закрываем executorService (если этого не сделать, то процесс никогда не закончится). При следующих входных параметрах
Я получил следующий output.
Я не знаю какие вопросы могут быть. По-этому оставляю минимум пояснений и прошу задавать свои вопросы в комментариях.
В дополнение к этому посту я приведу еще бенчмарк который покажет разницу в выполнении этой задачи многопоточно и однопоточно