оригинал:Java 8 CompletableFutures Part I
- Автор: Билл Беджек
- Переводчик: никто
Предисловие переводчика
Добавлен JDK1.5FutureИнтерфейс, но использование интерфейса не очень подходит для нужд асинхронной разработки, да и пользоваться им не так удобно. Так что есть много сторонних пакетов
Future
, Гуава обеспечивает лучшееListenableFutureкласс, а Netty предоставляет один из своихFuture
. Итак, в Java8CompletableFuture
так сказатьFuture
С некоторыми болями вы можете элегантно выполнять составное асинхронное программирование, и в то же время оно больше соответствует функциональному программированию.
Java 8 отсутствовала долгое время, и в нее был добавлен отличный инструмент управления параллелизмом, которыйCompletableFutureДобрый.CompletableFuture
ДостигнутоFutureинтерфейса, и он может задавать значения явно, что более интересно, мы можем обрабатывать цепочки и поддерживать зависимые поведения, которые определяютсяCompletableFuture
Активируется завершением.CompletableFuture
Похоже на гуавуListenableFutureДобрый. Оба они обеспечивают схожую функциональность и не будут сравниваться в этой статье. я был в предыдущемстатьявведен вListenableFutrue
. Хотя дляListenableFutrue
Введение немного устарело, но подавляющее большинство знаний все еще применимо.CompletableFuture
Документация уже очень обширна, но в ней отсутствуют конкретные примеры того, как их использовать. Эта статья предназначена для того, чтобы показать, как использовать серию простых примеров в модульном тестировании.CompletableFuture
. Изначально хотел закончить в одной статьеCompleteableFuture
, но информации так много, что кажется лучше разделить на три части:
- Создавайте/составляйте задачи и добавляйте к ним слушателей.
- Обработка ошибок и восстановление после ошибок.
- Отменить или принудительно завершить.
Начало работы с CompletableFuture
НачинаяCompletableFuture
Перед этим нам потребуются некоторые базовые знания.CompletableFuture
ДостигнутоCompletionStageинтерфейс. Кратко описано в javadocCompletionStage
:
Возможно, асинхронно вычисляемый этап, выполняющий операцию или вычисляющий значение после завершения другого CompletionStage. Завершение этапа зависит от результата его собственного расчета, который, в свою очередь, может инициировать другие зависимые этапы.
Полная документация CompletionStage содержит большое количество контента, поэтому мы суммируем здесь несколько ключевых моментов:
-
можно рассчитать поFuture,Consumer или Runnableв интерфейсеapply,accept или runвыражается другими способами.
-
Выполнение расчета в основном включает в себя следующее
А. Выполнение по умолчанию (может вызвать поток)
б. Используйте значение по умолчанию
CompletionStage
Поставщик асинхронного выполнения для асинхронного выполнения. Эти имена методов используютsomeActionAsyncЭтот формат представляет.в. использоватьExecutorПоставщик выполняется асинхронно. Эти методы такжеsomeActionAsyncэтот формат, но добавляет
Executor
параметр.
Далее я буду цитировать непосредственно в этой статьеCompletableFuture
а также CompletionStage
.
Создать CompleteableFuture
СоздаватьCompleteableFuture
Просто, но не очень понятно. Самый простой способ - использоватьCompleteableFuture.completedFuture
метод, который возвращает новый и завершенныйCompleteableFuture
:
@Test
public void test_completed_future() throws Exception {
String expectedValue = "the expected value";
CompletableFuture<String> alreadyCompleted = CompletableFuture.completedFuture(expectedValue);
assertThat(alreadyCompleted.get(), is(expectedValue));
}
Это выглядит немного скучно, позже мы увидим, как создать завершенныйCompleteableFuture
пригодится.
Теперь давайте посмотрим, как создать представление асинхронной задачи.CompleteableFuture
:
private static ExecutorService service = Executors.newCachedThreadPool();
@Test
public void test_run_async() throws Exception {
CompletableFuture<Void> runAsync = CompletableFuture.runAsync(() -> System.out.println("running async task"), service);
//utility testing method
pauseSeconds(1);
assertThat(runAsync.isDone(), is(true));
}
@Test
public void test_supply_async() throws Exception {
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(simulatedTask(1, "Final Result"), service);
assertThat(completableFuture.get(), is("Final Result"));
}
В первом способе мы видимrunAsync
задача, во втором способе, тоsupplyAsync
пример. Это может быть очевидно, однако использованиеrunAsync
или использоватьsupplyAsync
, в зависимости от того, имеет ли задача возвращаемое значение. В обоих примерах мы предоставляем пользовательскийExecutor
, который действует как поставщик асинхронного выполнения. когда используешьsupplyAsync
метод, я лично думаю, используяCallableвместоSupplier
Это кажется более естественным. потому что онифункциональный интерфейс,Callable
более тесно связан с асинхронными задачами и также может генерировать проверенные исключения, в то время какSupplier
то нет (хотя мы можем передать небольшое количество кодаПозволятьSupplier
генерировать проверенное исключение).
добавить слушателя
Теперь мы можем создатьCompleteableFuture
Объекты. Чтобы запустить асинхронные задачи, давайте начнем учиться завершать задачи «аудирования» и выполнять некоторые из следующих движений. Здесь вы следуете, когда вы увеличиваетеCompletionStage
Когда объект отслеживается, предыдущая задача должна быть полностью успешной, прежде чем можно будет запустить последующие задачи и этапы. В этой статье будут представлены некоторые методы работы с неудавшимися задачами, в то время как вCompleteableFuture
Решение для работы с ошибками в цепочке будет представлено в следующих статьях.
@Test
public void test_then_run_async() throws Exception {
Map<String,String> cache = new HashMap<>();
cache.put("key","value");
CompletableFuture<String> taskUsingCache = CompletableFuture.supplyAsync(simulatedTask(1,cache.get("key")),service);
CompletableFuture<Void> cleanUp = taskUsingCache.thenRunAsync(cache::clear,service);
cleanUp.get();
String theValue = taskUsingCache.get();
assertThat(cache.isEmpty(),is(true));
assertThat(theValue,is("value"));
}
Этот пример в основном показан в первомCompletableFuture
После успешного завершения запустите задачу очистки. В предыдущем примере, когда начальная задача завершилась успешно, мы использовалиRunnable
выполнение задачи. Мы также можем определить последующую задачу, которая может напрямую получить успешный результат предыдущей задачи.
@Test
public void test_accept_result() throws Exception {
CompletableFuture<String> task = CompletableFuture.supplyAsync(simulatedTask(1, "add when done"), service);
CompletableFuture<Void> acceptingTask = task.thenAccept(results::add);
pauseSeconds(2);
assertThat(acceptingTask.isDone(), is(true));
assertThat(results.size(), is(1));
assertThat(results.contains("add when done"), is(true));
}
это использованиеAcceptПример метода, который получаетCompletableFuture
, затем передать результатConsumer
объект. В Java 8,Consumer
У экземпляров нет возвращаемого значения. Если вы хотите получить побочные эффекты запуска, вам нужно поместить результаты в список.
композиция и композиционные задачи
В дополнение к добавлению слушателей для запуска последующих задач или принятияCompletableFuture
, мы также можем комбинировать или составлять задачи.
Композиционное задание
Составление означает получение успешногоCompletableFuture
результат в качестве входных данных черезFunctionвернуть другойCompletableFuture
. Ниже приведено использованиеCompletableFuture.thenComposeAsync
пример:
@Test
public void test_then_compose() throws Exception {
Function<Integer,Supplier<List<Integer>>> getFirstTenMultiples = num ->
()->Stream.iterate(num, i -> i + num).limit(10).collect(Collectors.toList());
Supplier<List<Integer>> multiplesSupplier = getFirstTenMultiples.apply(13);
//Original CompletionStage
CompletableFuture<List<Integer>> getMultiples = CompletableFuture.supplyAsync(multiplesSupplier, service);
//Function that takes input from orignal CompletionStage
Function<List<Integer>, CompletableFuture<Integer>> sumNumbers = multiples ->
CompletableFuture.supplyAsync(() -> multiples.stream().mapToInt(Integer::intValue).sum());
//The final CompletableFuture composed of previous two.
CompletableFuture<Integer> summedMultiples = getMultiples.thenComposeAsync(sumNumbers, service);
assertThat(summedMultiples.get(), is(715));
}
В этом примере первыйCompletionStage
Приводится список, содержащий 10 чисел, каждое из которых умножается на 13. это обеспечиваетFunction
получить эти результаты и создать другойCompletionStage
, который суммирует числа в списке.
Объединяйте задачи
Комбинированная задача завершается получением двух успешныхCompletionStages
И вывестиBiFunctionПараметры типа, которые, в свою очередь, дают дополнительные результаты. Ниже приведен очень простой пример, иллюстрирующий комбинациюCompletionStages
чтобы получить результаты.
@Test
public void test_then_combine_async() throws Exception {
CompletableFuture<String> firstTask = CompletableFuture.supplyAsync(simulatedTask(3, "combine all"), service);
CompletableFuture<String> secondTask = CompletableFuture.supplyAsync(simulatedTask(2, "task results"), service);
CompletableFuture<String> combined = firstTask.thenCombineAsync(secondTask, (f, s) -> f + " " + s, service);
assertThat(combined.get(), is("combine all task results"));
}
В этом примере показано, как объединить две асинхронные задачи.CompletionStage
, однако мы также можем объединить уже сделанноеCompletableFuture
асинхронная задача. Объединение известного значения, которое необходимо вычислить, также является хорошим способом сделать это:
@Test
public void test_then_combine_with_one_supplied_value() throws Exception {
CompletableFuture<String> asyncComputedValue = CompletableFuture.supplyAsync(simulatedTask(2, "calculated value"), service);
CompletableFuture<String> knowValueToCombine = CompletableFuture.completedFuture("known value");
BinaryOperator<String> calcResults = (f, s) -> "taking a " + f + " then adding a " + s;
CompletableFuture<String> combined = asyncComputedValue.thenCombine(knowValueToCombine, calcResults);
assertThat(combined.get(), is("taking a calculated value then adding a known value"));
}
Наконец, это использованиеCompletableFuture.runAfterbothAsync
пример
@Test
public void test_run_after_both() throws Exception {
CompletableFuture<Void> run1 = CompletableFuture.runAsync(() -> {
pauseSeconds(2);
results.add("first task");
}, service);
CompletableFuture<Void> run2 = CompletableFuture.runAsync(() -> {
pauseSeconds(3);
results.add("second task");
}, service);
CompletableFuture<Void> finisher = run1.runAfterBothAsync(run2,() -> results. add(results.get(0)+ "&"+results.get(1)),service);
pauseSeconds(4);
assertThat(finisher.isDone(),is(true));
assertThat(results.get(2),is("first task&second task"));
}
Слушайте первое законченное задание
Во всех предыдущих примерах все результаты должны ждать всехCompletionStage
Конец, однако, в том, что спрос не всегда такой. Возможно, нам понадобится получить результат первой выполненной задачи. В следующем примере показано использованиеConsumer
Принять первый завершенный результат:
@Test
public void test_accept_either_async_nested_finishes_first() throws Exception {
CompletableFuture<String> callingCompletable = CompletableFuture.supplyAsync(simulatedTask(2, "calling"), service);
CompletableFuture<String> nestedCompletable = CompletableFuture.supplyAsync(simulatedTask(1, "nested"), service);
CompletableFuture<Void> collector = callingCompletable.acceptEither(nestedCompletable, results::add);
pauseSeconds(2);
assertThat(collector.isDone(), is(true));
assertThat(results.size(), is(1));
assertThat(results.contains("nested"), is(true));
}
аналогичная функцияCompletableFuture.runAfterEither
@Test
public void test_run_after_either() throws Exception {
CompletableFuture<Void> run1 = CompletableFuture.runAsync(() -> {
pauseSeconds(2);
results.add("should be first");
}, service);
CompletableFuture<Void> run2 = CompletableFuture.runAsync(() -> {
pauseSeconds(3);
results.add("should be second");
}, service);
CompletableFuture<Void> finisher = run1.runAfterEitherAsync(run2,() -> results.add(results.get(0).toUpperCase()),service);
pauseSeconds(4);
assertThat(finisher.isDone(),is(true));
assertThat(results.get(1),is("SHOULD BE FIRST"));
}
несколько комбинаций
Пока все комбинации/конфигурации имеют только два примера.CompletableFuture
объект. Это сделано намеренно, чтобы сделать пример максимально простым и понятным. Мы можем объединить любое количествоCompletionStage
. Обратите внимание, что приведенные ниже примеры приведены только для иллюстрации!
@Test
public void test_several_stage_combinations() throws Exception {
Function<String,CompletableFuture<String>> upperCaseFunction = s -> CompletableFuture.completedFuture(s.toUpperCase());
CompletableFuture<String> stage1 = CompletableFuture.completedFuture("the quick ");
CompletableFuture<String> stage2 = CompletableFuture.completedFuture("brown fox ");
CompletableFuture<String> stage3 = stage1.thenCombine(stage2,(s1,s2) -> s1+s2);
CompletableFuture<String> stage4 = stage3.thenCompose(upperCaseFunction);
CompletableFuture<String> stage5 = CompletableFuture.supplyAsync(simulatedTask(2,"jumped over"));
CompletableFuture<String> stage6 = stage4.thenCombineAsync(stage5,(s1,s2)-> s1+s2,service);
CompletableFuture<String> stage6_sub_1_slow = CompletableFuture.supplyAsync(simulatedTask(4,"fell into"));
CompletableFuture<String> stage7 = stage6.applyToEitherAsync(stage6_sub_1_slow,String::toUpperCase,service);
CompletableFuture<String> stage8 = CompletableFuture.supplyAsync(simulatedTask(3," the lazy dog"),service);
CompletableFuture<String> finalStage = stage7.thenCombineAsync(stage8,(s1,s2)-> s1+s2,service);
assertThat(finalStage.get(),is("THE QUICK BROWN FOX JUMPED OVER the lazy dog"));
}
Обратите внимание, что порядок не гарантируется при объединении CompletionStages. В этих модульных тестах предоставляется время для моделирования задач, чтобы обеспечить порядок выполнения.
резюме
В этой статье в основном используетсяCompletableFuture
Первая часть класса. В последующих статьях мы в основном будем вводить обработку ошибок и восстановление, принудительное завершение или отмену.
ресурс
Подписывайтесь на меня: