Callable и Future

Открыта

Проблема 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 >
        future = new FutureTask & lt; >
        (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
Комментарии (22)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
Anonymous #3491453
Уровень 49
28 июля, 12:06
Как в последней задаче у правильного решения может быть такой вывод?
Начинает играть: скрипка. Продолжительность: 931ms
Начинает играть: тромбон. Продолжительность: 1021ms
Начинает играть: бубенцы. Продолжительность: 97ms
Начинает играть: контрабас. Продолжительность: 425ms
бубенцы заканчивает играть
контрабас заканчивает играть
тромбон заканчивает играть
Успели доиграть: [тромбон, бубенцы, контрабас]
Тромбон не попадает в 800 мс, но при этом он в списке тех, кто успел доиграть... ЗЫ Интересно, в скольких ещё задачах на JavaRush такие "правильные" решения?
Underdante
Уровень 52
7 июня, 12:02
я последнюю задачку не понял совсем. мы ждем завершения игры 800мс у каждого интрумента. Ведь в цикле это все сделано. Т.е. стартуют все вместе но последнии элемент в коллекции успеет доиграть , ведь ему отведено аж 3,2 секунды при максимально возможной игре 2000мс. в чем прикол не понятно. ладно бы если на весь концерт давалось 800 мс , но тут какая то странная логика. первому инструменту 800 , второму 800*2,третьему 800*3 и т.д.
КириллQA Engineer в Sogaz-OMS
24 января, 22:31
🤡🤡🤡
КириллQA Engineer в Sogaz-OMS
24 января, 21:57
что такое Callable & lt; Integer & gt; А именно % it и % gt????????? откуда эти связки?
Mary
Уровень 90
29 января, 17:06
это HTMLка шалит. На самом деле это <Integer>.
Андрей
Уровень 115
12 октября 2025, 09:07
Я возможно упустил, но в лекции для метода V get(Long time, TimeUnit unit) не указано, что при выполнении задачи сверх заданного лимита вылетает исключение TimeoutExepction. Может пометить в лекции под методом? (Не кидайтесь в меня камнями, возможно эта мелочная инфа не всем нужна и она очевидная, но все же напомнить об этом может многим помочь).
I'm Siberian Trust me, i'm an engineer Expert
19 сентября 2024, 12:57
Благодаря этой лекции и задачам, я понял, что я них*ена не понял. пошел гуглить FutureTask ещё где-нить...
Дмитрий Саргаевco-founder в formatiqa.comExpert
1 марта 2023, 14:07
task2806 почему это спокойно работает? не надо перехватывать исключения
for (FutureTask<Link> task : tasks) {
    System.out.println(task.get());
}
а это нет? надо перехватывать
tasks.forEach(task -> System.out.println(task.get()));
Андрей Пазюк
Уровень 117
Expert
25 июля 2023, 02:49
Дмитрий, в обоих примерах метод get() кидает исключение. Возможно вы его в первом примере просто обработали или пробросили дальше в сигнатуре метода, где находится данный код.
Глеб
Уровень 108
Expert
11 января 2023, 17:06
ctrl + alt + l
Greg
Уровень 108
Expert
29 октября 2022, 08:19
Строка 1 Строка 2
AlexanderРаботает в СканпортExpert
16 октября 2022, 14:08
Зачем{ Так Много } Пустых Строк ? Неудобно же