Проблема 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) {
result = second;
} else {
for (int i = 0; i < 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);
}
В консоль будет выведено:
У этого кода есть ряд недостатков. Например, главная нить будет заблокирована на время вызова метода 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, тут можно не следить за проверяемыми исключениями, которые возникают внутри метода:
|
|
Интерфейс 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<Integer> {
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 < index - 2; i++) {
int temp = second;
second += first;
first = temp;
}
return second;
}
}
public static Future<Integer> calculateAsync(int index) throws Exception {
Fibonacci fibonacci = new Fibonacci(index);
// объект future будет представлять результат вычислений задачи fibonacci
FutureTask<Integer> future = new FutureTask<>(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);
}
}
В консоль будет выведено:
17-е число Фибоначчи: 987
18-е число Фибоначчи: 1597
19-е число Фибоначчи: 2584
10-е число Фибоначчи: 34
11-е число Фибоначчи: 55
12-е число Фибоначчи: 89
13-е число Фибоначчи: 144
14-е число Фибоначчи: 233
15-е число Фибоначчи: 377
V get(long timeout, TimeUnit unit)
— как и предыдущий метод, блокирует вызвавшую его нить в ожидании результата, но только на время, заданное параметрами метода. Уточнение! Если результата нет по истечении timeout, бросаетTimeoutException
! // т.к. класс FutureTask реализует и интерфейс Future и интерфейс Runnable, можно передавать его экземпляры в конструктор Thread Нужно хоть пару слов написать оFutureTask
перед тем как давать его в примерах.