Подписаться на хаб

Java dev

Клуб Java разработчиков...
В закладки
3419
1
2

Будущее или интерфейс 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);

long fact = executorService.submit(new Factorial(factIn)).get();
double hypotenuse = executorService.submit(new Hypotenuse(cath1, cath2)).get();
int nod = executorService.submit(new NOD(nod1, nod2)).get();

System.out.printf(
"Факториал: %20d%nГипотенуза: %21.2f%nНОД: %25d%nСумма всех значений: %13.2f",
fact, hypotenuse, nod, (fact + hypotenuse + nod)
);

executorService.shutdown();

Самое интересное тут. Создаем FixedThreadPool из трех потоков. Далее используем наш ExecutorService чтобы параллельно вычислить наши величины и выводим результат вычисления на консоль. После чего закрываем executorService (если этого не сделать, то процесс никогда не закончится). При следующих входных параметрах

Я получил следующий output.

Я не знаю какие вопросы могут быть. По-этому оставляю минимум пояснений и прошу задавать свои вопросы в комментариях.

Пожаловаться Подписаться
3419
1
2
1 ответ
Вячеслав@proweber1

В дополнение к этому посту я приведу еще бенчмарк который покажет разницу в выполнении этой задачи многопоточно и однопоточно