Callable call java для чего используется
Перейти к содержимому

Callable call java для чего используется

  • автор:

Concurrent интерфейсы Callable, Future

При работе многопоточного приложения часто необходимо получение от потока результата его деятельности в виде некоторого объекта. Эту задачу можно решить с использованием интерфейсов Callable и Future. Совместное использование двух реализаций данных интерфейсов позволяет получить результат в виде некоторого объекта.

Интерфейс Callable

Интерфейс Callable очень похож на интерфейс Runnable. Объекты, реализующие данные интерфейсы, исполняются другим потоком. Однако, в отличие от Runnable, интерфейс Callable использует Generic’и для определения типа возвращаемого объекта. Runnable содержит метод run(), описывающий действие потока во время выполнения, а Callable – метод call().

С документацией интерфейса Callable можно познакомиться здесь.

Интерфейс Future

Интерфейс Future также, как и интерфейс Callable, использует Generic’и. Методы интерфейса можно использовать для проверки завершения работы потока, ожидания завершения и получения результата. Результат выполнения может быть получен методом get, если поток завершил работу. Прервать выполнения задачи можно методом cancel. Дополнительные методы позволяют определить завершение задачи : нормальное или прерванное. Если задача завершена, то прервать ее уже невозможно.

Методы интерфейса Future
Метод Описание
cancel (boolean mayInterruptIfRunning) попытка завершения задачи
V get() ожидание (при необходимости) завершения задачи, после чего можно будет получить результат
V get(long timeout, TimeUnit unit) ожидание (при необходимости) завершения задачи в течение определенного времени, после чего можно будет получить результат
isCancelled() вернет true, если выполнение задачи будет прервано прежде завершения
isDone() вернет true, если задача завершена

С документацией интерфейса Future можно познакомиться здесь.

Пример использования интерфейсов Callable, Future

Рассмотрим простейший пример использования интерфейсов Callable и Future. Основная идея данного примера – показать, как можно, используя Future, узнать статус Callable потока и получить возвращенный объект. В примере используется объект executor типа ExecutorService, формирующий пул из трех потоков. Метод submit с параметром Callable возвращает объект Future для каждого из стартуемого потоков.

import java.util.Date; import java.util.List; import java.util.ArrayList; import java.util.concurrent.Future; import java.util.concurrent.Callable; import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutionException; import java.text.SimpleDateFormat; public class CallableExample < public CallableExample() < // Определяем пул из трех потоков ExecutorService executor; executor = Executors.newFixedThreadPool(3); // Список ассоциированных с Callable задач Future List> futures; futures = new ArrayList>(); // Создание экземпляра Callable класса Callable callable = new CallableClass(); for (int i = 0; i < 3; i++)< /* * Стартуем возвращаюший результат исполнения * в виде объекта Future поток */ Futurefuture; future = executor.submit(callable); /* * Добавляем объект Future в список для * отображения результат выполнения (получение * наименования потока) */ futures.add(future); > SimpleDateFormat sdf; sdf = new SimpleDateFormat("HH:mm:ss "); for (Future future : futures) < try < // Выводим в консоль полученное значение String text = sdf.format(new Date()) + future.get(); System.out.println(text); >catch (InterruptedException | ExecutionException e) <> > // Останавливаем пул потоков executor.shutdown(); > //----------------------------------------------------- // Класс, реализующий интерфейс Callable class CallableClass implements Callable  < @Override public String call() throws Exception < Thread.sleep(1000); // наименование потока, выполняющего // callable задачу return Thread.currentThread().getName(); >> //----------------------------------------------------- public static void main(String args[]) < new CallableExample(); >>

Класс CallableClass, реализующий интерфейс Callable, использует объект String в качестве generic’a. Соответственно и каждый объект Future также должен использовать тип объекта String.

Результат выполнения

Перед остановкой пула потоков в консоль выводятся наименования потока. Т.е. в примере демонстрируется возможность не прямого обращения к методу call класса, реализующего интерфейс Callable, а косвенно через объект Future, полученного при старте потока.

17:41:16 pool-1-thread-1 17:41:19 pool-1-thread-2 17:41:19 pool-1-thread-3

Интерфейсы Callable и Future в Java

Интерфейсы 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, реализуя оба интерфейса.

Например:

Thread’ом Java не испортишь: Часть IV — Callable, Future и друзья

Java-университет

Thread

Мы уже рассматривали в первой части, как создаются потоки. Ещё раз вспомним. Поток — это 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

Thread

Шло время, и в 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)); 

Thread

Тут хочется отметить, что для краткости использован метод 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 call java для чего используется

java.util.concurrent

Interface Callable

  • Type Parameters: V — the result type of method call All Known Subinterfaces: DocumentationTool.DocumentationTask, JavaCompiler.CompilationTask Functional Interface: This is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference.
@FunctionalInterface public interface Callable

A task that returns a result and may throw an exception. Implementors define a single method with no arguments called call . The Callable interface is similar to Runnable , in that both are designed for classes whose instances are potentially executed by another thread. A Runnable , however, does not return a result and cannot throw a checked exception. The Executors class contains utility methods to convert from other common forms to Callable classes.

Method Summary

All Methods Instance Methods Abstract Methods

Modifier and Type Method and Description
V call ()

Computes a result, or throws an exception if unable to do so.

Method Detail

call
V call() throws Exception

Computes a result, or throws an exception if unable to do so.

Java™ Platform
Standard Ed. 8

Submit a bug or feature
For further API reference and developer documentation, see Java SE Documentation. That documentation contains more detailed, developer-targeted descriptions, with conceptual overviews, definitions of terms, workarounds, and working code examples.
Copyright © 1993, 2023, Oracle and/or its affiliates. All rights reserved. Use is subject to license terms. Also see the documentation redistribution policy.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *