Проблема 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);
}

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

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<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);
    		}
}

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

16-е число Фибоначчи: 610
17-е число Фибоначчи: 987
18-е число Фибоначчи: 1597
19-е число Фибоначчи: 2584
10-е число Фибоначчи: 34
11-е число Фибоначчи: 55
12-е число Фибоначчи: 89
13-е число Фибоначчи: 144
14-е число Фибоначчи: 233
15-е число Фибоначчи: 377