Интерфейсы Callable и Future в Java
Интерфейс Java Callable(java.util.concurrent.Callable) представляет асинхронную задачу, которая может быть выполнена отдельным потоком. Например, можно передать объект Callable в Java ExecutorService, который затем выполнит его асинхронно. Метод call() вызывается для выполнения асинхронной задачи.
Интерфейс Callable довольно прост. Он содержит единственный метод с именем call().
public interface Callable
Если задача выполняется асинхронно, результат обычно передается обратно через Java Future. Это тот случай, когда Callable передается в ExecutorService для одновременного выполнения.

Callable использует Generic для определения типа возвращаемого объекта. Класс Executors предоставляет полезные методы для выполнения Java Callable в пуле потоков. Поскольку вызываемые задачи выполняются параллельно, нам нужно дождаться возвращенного объекта.
Callable задачи возвращают объект java.util.concurrent.Future. Используя объект Java Future, мы можем узнать состояние задачи Callable и получить возвращенный объект. Он предоставляет метод get(), который может ожидать завершения Callable и затем возвращать результат.
Future предоставляет метод cancel() для отмены связанной задачи Callable. Существует версия метода get(), в которой мы можем указать время ожидания результата, поэтому полезно избегать блокировки текущего потока на более длительное время.
Существуют методы isDone() и isCancelled() для определения текущего состояния связанной вызываемой задачи.
Вот простой пример задачи с Callable, которая возвращает имя потока, выполняющего задачу через одну секунду. Мы используем платформу Executor для параллельного выполнения 100 задач и используем Java Future для получения результата представленных задач.
package com.journaldev.threads; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class MyCallable implements Callable < @Override public String call() throws Exception < Thread.sleep(1000); //вернуть имя потока, выполняющего вызываемую задачу return Thread.currentThread().getName(); >public static void main(String args[]) < //Получить ExecutorService из служебного класса Executors //размер пула потоков равен 10 ExecutorService executor = Executors.newFixedThreadPool(10); //создать список для хранения объекта Future, связанного с Callable Listlist = new ArrayList(); //Create MyCallable instance Callable callable = new MyCallable(); for(int i=0; i < 100; i++)< Future future = executor.submit(callable); //добавив Future в список, мы можем получить возвращаемое значение list.add(future); >for(Future fut : list) < try < // выводим возвращаемое значение Future, замечаем задержку вывода в консоли // потому что Future.get() ожидает завершения задачи System.out.println(new Date()+ "::"+fut.get()); >catch (InterruptedException | ExecutionException e) < e.printStackTrace(); >> //закрыть службу executor.shutdown(); > >
После того, как мы выполним вышеуказанную программу, вы заметите задержку вывода, потому что метод get() ожидает завершения задачи, вызываемой Java. Также обратите внимание, что есть только 10 потоков, выполняющих эти задачи.
Вот фрагмент вывода вышеуказанной программы.
Mon Dec 31 20:40:15 PST 2012::pool-1-thread-1
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-2
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-3
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-4
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-5
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-6
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-7
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-8
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-9
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-10
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-2
.
Что делать, если мы хотим переопределить некоторые методы, например, переопределить метод get() для тайм-аута через некоторое время по умолчанию, а не ждать бесконечно?
В этом случае пригодится класс Java FutureTask, который является базовой реализацией Future.
Средняя оценка 4.3 / 5. Количество голосов: 13
Thread’ом Java не испортишь: Часть IV — Callable, Future и друзья


Мы уже рассматривали в первой части, как создаются потоки. Ещё раз вспомним. Поток — это Thread , в нём что-то запускается run , поэтому воспользуемся tutorialspoint java online compiler’ом и выполним следующий код:
public class HelloWorld < public static void main(String []args)< Runnable task = () ->< System.out.println("Hello World"); >; new Thread(task).start(); > >
Единтсвенный ли это вариант запуска задачи в потоке?
java.util.concurrent.Callable
Оказывается, у java.lang.Runnable есть брат и зовут его java.util.concurrent.Callable и появился он на свет в Java 1.5. В чём же различия? Если приглядеться к JavaDoc этого интерфейса, мы видим, что в отличие от Runnable , новый интерфейс объявляет метод call , который возвращает результат. Кроме того, по умолчанию он throws Exception. То есть избавляет нас от необходимости на проверяемые исключения писать try-catch блоки. Уже неплохо, правда? Теперь у нас есть вместо Runnable новый task:
Callable task = () -> < return "Hello, World!"; >;
Но что с ним делать? Зачем нам вообще задача, выполняемая в потоке, которая возвращает результат? Очевидно, что в дальнейшем мы рассчитываем получить результат действий, которыев в будущем будут выполнены. Будущее по-английский — Future. И интерфейс есть с точно таким же именем: java.util.concurrent.Future
java.util.concurrent.Future
Интерфейс java.util.concurrent.Future описывает API для работы с задачами, результат которых мы планируем получить в будущем: методы получения результата, методы проверки статуса. Для Future нас интересует его реализация java.util.concurrent.FutureTask. То есть это Task , который будет выполнен во Future . Чем эта реализация ещё интересна, так это тем, что она реализует и Runnable . Можно считать это своего рода адаптером старой модели работы с задачами в потоках и новой модели (новой в том смысле, что она появилась в java 1.5). Вот пример:
import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class HelloWorld < public static void main(String []args) throws Exception < Callable task = () ->< return "Hello, World!"; >; FutureTask future = new FutureTask<>(task); new Thread(future).start(); System.out.println(future.get()); > >
Как видно из примера, мы получаем при помощи метода get результат из задачи task . (!)Важно , что в момент получения результата при помощи метода get выполнение становится синхронным. Как вы думаете, какой механизм тут будет использован? Правильно, нет блока синхронизации — поэтому WAITING в JVisualVM мы увидим не как monitor или wait , а как тот самый park (т.к. используется механизм LockSupport ).
Функциональные интерфейсы
Дальше пойдёт речь про классы из Java 1.8, поэтому не лишним будет сделать краткое введение. Посмотрим на следующий код:
Supplier supplier = new Supplier() < @Override public String get() < return "String"; >>; Consumer consumer = new Consumer() < @Override public void accept(String s) < System.out.println(s); >>; Function converter = new Function() < @Override public Integer apply(String s) < return Integer.valueOf(s); >>;
Как же много лишнего кода, не правда ли? Каждый из объявляемых классов выполняет какую-то одну функцию, но для её описания мы используем кучу лишнего вспомогательного кода. И разработчики Java так же подумали. Поэтому, они ввели набор «функциональных интерфейсов» ( @FunctionalInterface ) и решили, что теперь Java сама будет «додумывать» за нас всё, кроме важного:
Supplier supplier = () -> "String"; Consumer consumer = s -> System.out.println(s); Function converter = s -> Integer.valueOf(s);
Supplier — поставщик. Он не имеет параметров, но возвращает что-то, то есть поставляет это. Consumer — потребитель. Он принимает на вход что-то (параметр s) и с этим что-то что-то делает, то есть потребляет что-то. Есть ещё функция. Она принимает на вход что-то (параметр s ), что-то делает и возвращает что-то. Как мы видим, активно используются дженерики. В случае неуверенности можно вспомнить про них и прочитать «Теория дженериков в Java или как на практике ставить скобки».
CompletableFuture

Шло время, и в Java 1.8 появился новый класс, который зовётся CompletableFuture . Он реализует интерфейс Future , то есть наши task будут выполнены в будущем, и мы сможем выполнить get и получить результат. Но ещё он реализует некоторый CompletionStage . Из перевода уже понятно его назначение: это некий этап (Stage) каких-то вычислений. С кратким введением в тему можно ознакомиться в обзоре «Introduction to CompletionStage and CompletableFuture». Давайте перейдём сразу к делу. Посмотрим на список доступных статических методов, которые нам помогут начать: Вот варианты их использования:
import java.util.concurrent.CompletableFuture; public class App < public static void main(String []args) throws Exception < // CompletableFuture уже содержащий результат CompletableFuturecompleted; completed = CompletableFuture.completedFuture("Просто значение"); // CompletableFuture, запускающий (run) новый поток с Runnable, поэтому он Void CompletableFuture voidCompletableFuture; voidCompletableFuture = CompletableFuture.runAsync(() -> < System.out.println("run " + Thread.currentThread().getName()); >); // CompletableFuture, запускающий новый поток, результат которого возьмём у Supplier CompletableFuture supplier; supplier = CompletableFuture.supplyAsync(() -> < System.out.println("supply " + Thread.currentThread().getName()); return "Значение"; >); > >
Если мы выполним этот код, то увидим, что создание CompletableFuture подразумевает запуск и всей цепочки. Поэтому при некоторой схожести со SteamAPI из Java8 в этом отличие этих подходов. Например:
List array = Arrays.asList("one", "two"); Stream stringStream = array.stream().map(value -> < System.out.println("Executed"); return value.toUpperCase(); >);
- У нас есть функция ( Function ), которая принимает А и возвращает Б. Имеет единственный метод — apply (применить).
- У нас есть потребитель ( Consumer ), которая принимает А и ничего не возвращает (Void). Имеет единственный метод — accept (принять).
- У нас есть запускаемый в потоке код Runnable , который не принимает и не возвращает. Имеет единственный метод — run (запустить).
public static void main(String []args) throws Exception < AtomicLong longValue = new AtomicLong(0); Runnable task = () ->longValue.set(new Date().getTime()); Function dateConverter = (longvalue) -> new Date(longvalue); Consumer printer = date -> < System.out.println(date); System.out.flush(); >; // CompletableFuture computation CompletableFuture.runAsync(task) .thenApply((v) -> longValue.get()) .thenApply(dateConverter) .thenAccept(printer); >
У методов thenRun , thenApply и thenAccept есть версии Async . Это значит, что эти стадии будут выполнены в новом потоке. Он будет взят из особого пула, поэтому заранее неизвестно, какой поток будет, новый или прежний. Всё зависит от того, на сколько тяжёлые задачи. Помимо этих методов есть ещё три интересные возможности. Для наглядности представим, что у нас есть некий сервис, который получает какое-то сообщение откуда-то и на это требуется время:
public static class NewsService < public static String getMessage() < try < Thread.currentThread().sleep(3000); return "Message"; >catch (InterruptedException e) < throw new IllegalStateException(e); >> >
Теперь, давайте посмотрим на другие возможности, которые предоставляет CompletableFuture . Мы можем объединять результат CompletableFuture с результатом другого CompletableFuture :
Supplier newsSupplier = () -> NewsService.getMessage(); CompletableFuture reader = CompletableFuture.supplyAsync(newsSupplier); CompletableFuture.completedFuture("!!") .thenCombine(reader, (a, b) -> b + a) .thenAccept(result -> System.out.println(result)) .get();
Тут стоить обратить внимание, что по умолчанию потоки будут демон-потоками, поэтому для наглядности мы используем get , чтобы дождаться результат. А ещё мы можем не только объединить (combine), но и возвращать CompletableFuture :
CompletableFuture.completedFuture(2L) .thenCompose((val) -> CompletableFuture.completedFuture(val + 2)) .thenAccept(result -> System.out.println(result));

Тут хочется отметить, что для краткости использован метод CompletableFuture.completedFuture . Данный метод не создаёт новый поток, поэтому остальная цепочка будет выполнена в том же потоке, в котором был вызван completedFuture . Также есть метод thenAcceptBoth . Он очень похож на accept , но если thenAccept принимает consumer , то thenAcceptBoth принимает на вход ещё один CompletableStage + BiConsumer , то есть consumer , который на вход принимает 2 источника, а не один. Есть ещё интересная возможность со словом Either : Данные методы принимают альтернативный CompletableStage и будут выполнены на том CompletableStage , который первее выполнится. И закончить этот обзор хочется ещё одной интересной возможностью CompletableFuture — обработкой ошибок.
CompletableFuture.completedFuture(2L) .thenApply((a) -> < throw new IllegalStateException("error"); >).thenApply((a) -> 3L) //.exceptionally(ex -> 0L) .thenAccept(val -> System.out.println(val));
- CompletableFuture. Хочется взять и применить (2015)
- CompletableFuture in Java 8, asynchronous processing done right
Заключение
- Пишем асинхронный код с CompletableFuture
- Introduction to CompletionStage and CompletableFuture
- Java 8 CompletableFuture. Часть 2
- Guide To CompletableFuture
| Что еще почитать: |
|---|
| Thread’ом Java не испортишь: Часть I — потоки Thread’ом Java не испортишь: Часть II — синхронизация Thread’ом Java не испортишь : Часть III — взаимодействие Thread’ом Java не испортишь: Часть V — Executor, ThreadPool, Fork Join Thread’ом Java не испортишь: Часть VI — К барьеру! |
Интерфейсы Callable и Future в Java

Интерфейс Runnable инкапсулирует задачу, выполняющуюся асинхронно. Вы можете воспринимать это как асинхронный метод без параметров и возвращаемого значения. Callable подобен Runnable, но с возвратом значения. Интерфейс Callable является параметризованным типом, с единственным общедоступным методом call().
public interface Callable ;
V call ( ) throws Exception ;
Параметр представляет собой тип возвращаемого значения. Например, Callable представляет асинхронное вычисление, которое в конечном итоге возвращает объект Integer.
Future хранит результат асинхронного вычисления. Вы можете запустить вычисление, предоставив кому-либо объект Future, и забыть о нем. Владелец объекта Future может получить результат, когда он будет готов.
Интерфейс Future имеет следующие методы.
public interface Future
V get ( ) throws . . . ;
V get ( long timeout , TimeUnit unit ) throws . . . ;
boolean isCancelled ( ) ;
boolean isDone ( ) ;
Вызов первого метода get() устанавливает блокировку до тех пор, пока не завершится вычисление. Второй метод генерирует исключение TimeoutException, если истекает таймаут до завершения вычислений. Если прерывается поток, выполняющий вычисление, оба метода генерируют исключение InterruptedException. Если вычисление уже завершено, get() немедленно возвращает управление.
Метод isDone() возвращает false, если вычисление продолжается, и true — если оно завершено.
Вы можете прервать вычисление,вызвав метод Cancel(). Если вычисление еще не стартовало, оно отменяется и уже не будет запущено. Если же вычисление уже идет, оно прерывается в случае равенства true параметра mayInterrupt.
Любите задавать вопросы в Аске, но Вам не хватает подписчиков и лайков — попробуйте заказать их на сервисе avi1.ru. Здесь Вы получите данные ресурсы по очень дешевой стоимости, а также с высококачественным исполнением от лучших сотрудников техподдержки.
Класс-оболочка FutureTask представляет собой удобный механизм для превращения Callable одновременно в Future и Runnable, реализуя оба интерфейса.
Например:
Как использовать Callable и FutureTask
Начиная с Java 1.5 в java.util.concurrent появился новый набор объектов. Этот пакет имеет несколько различных классов, включая очереди потоков. Я мог бы использовать их, когда программировал на Java 1.2! Когда я начал смотреть на новые игрушки, я стал колебаться. Что это за вызываемая вещь и каково будущее? Оказывается, в Future и Callable нет ничего плохого. На самом деле, это то, на что я надеялся в своей карьере Java.
Различия между Callable и Runnable
Callable – это то, на что надеялся Runnable. Единственный метод Callable – это «T call ()». Что делает его таким аккуратным, так это то, что он что-то возвращает. Это шаг над созданием геттера для ответа на задачу. Хотя это круто, должен быть способ получить возвращаемое значение.
Будущее здесь
Будущее способ вывести ценность, когда Callable будет сделано. Функция get () или get (длинный тайм-аут, единица времени). Это эквивалентно вызову thread.join (); runnable.getValue () одновременно.
пример
Я создал класс с именем CounterCallable. Все, что он делает, это добавляет числа от начала переменной до конца переменной.