Callable и Future

Модуль 2. Java Core
13 уровень , 3 лекция
Открыта

Проблема Runnable

Ты уже знаком с интерфейсом Runnable и реализующим его классом Thread. Давай вспомним, как выглядит этот интерфейс:


public interface Runnable {
	public abstract void run();
}

Обратим внимание, что у метода run возвращаемое значение — void. Но что, если нам нужно получить какой-то результат работы нити в виде числа, строки или любого другого объекта? Тогда придется как-то выкручиваться, например, так:


public class Fibonacci implements Runnable {

    private final int index;
    private int result;

    public Fibonacci(int index) {
        this.index = index;
    }

    @Override
    public void run() {

        int first = 0;
        int second = 1;

        if (index == 1) {
            result = first;
        } else if (index == 2) {x
            result = second;
        } else {

            for (int i = 0; i & lt; index - 2; i++) {
                int temp = second;
                second += first;
                first = temp;
            }
            result = second;
        }
    }

    public static void printByIndex(int index) throws InterruptedException {
        Fibonacci fibonacci = new Fibonacci(index);
        Thread thread = new Thread(fibonacci);
        thread.start();
        thread.join();
        System.out.println(index + "-е число Фибоначчи: " + fibonacci.result);
    }
}

Запустим такой метод main:


	public static void main(String[] args) throws Exception {
    		Fibonacci.printByIndex(10);
	}

В консоль будет выведено:

10-е число Фибоначчи: 34

У этого кода есть ряд недостатков. Например, главная нить будет заблокирована на время вызова метода printByIndex, так как он содержит вызов метода join.

Интерфейс Callable

Теперь давай рассмотрим интерфейс, который Java предоставляет нам “из коробки” и который может выступать альтернативой Runnable. Это интерфейс Callable:


public interface Callable<V> {
	V call() throws Exception;
}

Как видишь, у него так же, как и у Runnable, всего один метод. Назначение метода такое же, как и у метода run, — он будет содержать код, который будет выполнятся в параллельной нити. Из отличий можешь заметить возвращаемое значение. Теперь это может быть любой тип, который ты укажешь при реализации интерфейса:


public class CurrentDate implements Callable<Long> {
 
	@Override
	public Long call() {
    		return new Date().getTime();
	}
}

Еще пример:


Callable<String> task = () -> {
	Thread.sleep(100);
	return "Done";
};

Еще из полезностей: метод call может пробрасывать Exception, так что в отличии от метода run, тут можно не следить за проверяемыми исключениями, которые возникают внутри метода:


public class Sleep implements Runnable {

    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException ignored) {}
    }
}

public class Sleep implements Callable {

    @Override
    public Object call() throws InterruptedException {
        Thread.sleep(1000);
        return null;
    }
}

Интерфейс Future

Еще один интерфейс, который будет работать в тесной связке с Callable, — это Future. Future представляет результат асинхронных (параллельных) вычислений (то самое значение, которое возвращает метод call). Oн позволяет проверить, завершились ли вычисления, подождать завершения вычислений, получить результат вычислений, и другое.

Методы Future

  • boolean isDone() — метод возвращает true, если это задание (вычисления) завершено. Завершенными считаются задания, которые завершились штатным способом, завершились исключением или были отменены.

  • V get() — метод при необходимости блокирует нить, которая его вызвала, и по окончанию вычислений возвращает их результат.

  • V get(long timeout, TimeUnit unit) — как и предыдущий метод, блокирует вызвавшую его нить в ожидании результата, но только на время, заданное параметрами метода.

  • boolean cancel(boolean mayInterruptIfRunning) — метод пробует остановить выполнение задания. Если задача еще не начала выполняться, она уже никогда не запустится. Если задание было в процессе выполнения, то параметр mayInterruptIfRunning определяет, будет ли попытка прервать нить, выполняющую задание. После вызова метода cancel метод isDone всегда будет возвращать true.

  • boolean isCancelled() — метод возвращает true, если задание было отменено, не успев завершится штатным способом. Метод всегда будет возвращать true, если перед этим был вызван метод cancel, который вернул true.

Пример кода на Callable и Future


import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.*;

public class Fibonacci implements Callable & lt;
Integer & gt; {

    private final int index;
    public Fibonacci(int index) {
        this.index = index;
    }

    @Override
    public Integer call() {

        int first = 0;
        int second = 1;

        if (index == 1) {
            return first;
        } else if (index == 2) {
            return second;
        } else {
            for (int i = 0; i & lt; index - 2; i++) {
                int temp = second;
                second += first;
                first = temp;
            }
            return second;
        }
    }

    public static Future & lt;
    Integer & gt;
    calculateAsync(int index) throws Exception {

        Fibonacci fibonacci = new Fibonacci(index);
        // объект future будет представлять результат вычислений задачи fibonacci
        FutureTask & lt;
        Integer & gt;
        future = new FutureTask & lt; & gt;
        (fibonacci);


        // т.к. класс FutureTask реализует и интерфейс Future и интерфейс Runnable,
        // можно передавать его экземпляры в конструктор Thread
        Thread thread = new Thread(future);
        thread.start();
        return future;
    }
}

Запустим такой метод main:


            public static void main(String[] args) throws Exception {
                Map<Integer, Future<Integer>> tasks = new HashMap<>();
                        for (int i = 10; i < 20; i++) {
                                tasks.put(i, Fibonacci.calculateAsync(i));
                        }

                 for (Map.Entry<Integer, Future<Integer>> entry : tasks.entrySet()) {
                     Future<Integer> task = entry.getValue();
                      int index = entry.getKey();
                      int result;
                      // проверяем завершено ли задание
                      if (task.isDone()) {
                           // получаем результат вычислений
                           result = task.get();
                      } else {
                          try {
                          // ожидаем результат вычислений еще 100 миллисекунд
                           result = task.get(100, TimeUnit.MILLISECONDS);
                           } catch (TimeoutException e) {
                               // прерываем выполнение задания
                                   task.cancel(true);
                                   System.out.println("Вычисление " + index + "-го числа Фибоначчи не уложилось в отведенное время.");
                                         return;
                                        }
                                }
                                System.out.println(index + "-е число Фибоначчи: " + result);
                        }
                }

В консоль будет выведено:

16-е число Фибоначчи: 610
17-е число Фибоначчи: 987
18-е число Фибоначчи: 1597
19-е число Фибоначчи: 2584
10-е число Фибоначчи: 34
11-е число Фибоначчи: 55
12-е число Фибоначчи: 89
13-е число Фибоначчи: 144
14-е число Фибоначчи: 233
15-е число Фибоначчи: 377
Комментарии (17)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Андрей Уровень 66
12 октября 2025
Я возможно упустил, но в лекции для метода V get(Long time, TimeUnit unit) не указано, что при выполнении задачи сверх заданного лимита вылетает исключение TimeoutExepction. Может пометить в лекции под методом? (Не кидайтесь в меня камнями, возможно эта мелочная инфа не всем нужна и она очевидная, но все же напомнить об этом может многим помочь).
I'm Siberian Уровень 109 Expert
19 сентября 2024
Благодаря этой лекции и задачам, я понял, что я них*ена не понял. пошел гуглить FutureTask ещё где-нить...
Дмитрий Саргаев Уровень 76 Expert
1 марта 2023
task2806 почему это спокойно работает? не надо перехватывать исключения

        for (FutureTask<Link> task : tasks) {
            System.out.println(task.get());
        }
а это нет? надо перехватывать

    tasks.forEach(task -> System.out.println(task.get()));
Андрей Пазюк Уровень 117 Expert
25 июля 2023
Дмитрий, в обоих примерах метод get() кидает исключение. Возможно вы его в первом примере просто обработали или пробросили дальше в сигнатуре метода, где находится данный код.
Глеб Уровень 108 Expert
11 января 2023
ctrl + alt + l
Greg Уровень 108 Expert
29 октября 2022
Строка 1 Строка 2
Alexander Уровень 81 Expert
16 октября 2022
Зачем{ Так Много } Пустых Строк ? Неудобно же
Екатерина Уровень 70 Expert
17 сентября 2022
еще больше пустых строк кода в примерах кода... 🤢
Андрей Пазюк Уровень 117 Expert
2 июня 2022
Для тех, кто ломает голову о том, что такое FutureTask, то это класс который реализует интерфейс Future и все.
Александр Огарков Уровень 4 Expert
18 мая 2022
V get(long timeout, TimeUnit unit) — как и предыдущий метод, блокирует вызвавшую его нить в ожидании результата, но только на время, заданное параметрами метода. Уточнение! Если результата нет по истечении timeout, бросает TimeoutException! // т.к. класс FutureTask реализует и интерфейс Future и интерфейс Runnable, можно передавать его экземпляры в конструктор Thread Нужно хоть пару слов написать о FutureTask перед тем как давать его в примерах.
Юрий Уровень 56
23 апреля 2022
Администрация пофиксите примеры с кодом, зачем там столько места свободного, нужно скролить что бы увидеть ведь код, хотя он весь может поместиться на экране без того пустого пространства.
Сергей Уровень 108 Expert
19 мая 2022
Оказывается это только в мобильной версии косяк а если читать статью в браузере, то все норм отображается
Юрий Уровень 56
21 мая 2022
у меня с пк такая проблема, может дело разрешении монитора, адаптивная верстка мб плохо отрабатывает.
Марат Гарипов Уровень 108 Expert
25 июля 2022
Еще один повод скопировать код примера в IDEA и как следует разобраться;)
Дмитрий Уровень 66 Expert
20 июля 2024
Твой коммент отлично отражает отношение администрации и модераторов к пользователям платформы JavaRush, всем абсолютно пофиг на то, что мы пишем и просим пофиксить, коммент с просьбой удалить лишние пробелы в коде написан 19.04.2022, сейчас 20.07.2024
I'm Siberian Уровень 109 Expert
19 сентября 2024
2024.09.19 а воз и ныне там
Родион Уровень 113
25 июля 2025
25.07.2025, я думаю фикса в этом столетии ждать явно не стоит