Java8 — асинхронное программирование

Java

Java8 — асинхронное программирование

Асинхронное программирование

Так называемая асинхронность на самом деле заключается в реализации метода, которому не нужно ждать возвращаемого значения вызываемой функции и позволять операции продолжать выполняться.

Создавать и выполнять задачи

Создать без параметров

1 CompletableFuture<String> noArgsFuture = new CompletableFuture<>();

Передайте соответствующую задачу, без возвращаемого значения

runAsyncМетоды могут выполнять асинхронные вычисления в фоновом режиме, но в данный момент не возвращают значения. держи одинRunnableобъект.

1CompletableFuture noReturn = CompletableFuture.runAsync(()->{
2    //执行逻辑,无返回值
3});

Передайте в соответствующую задачу, есть возвращаемое значение

В этот момент мы видим, что возвращаетсяCompletableFuture<T>здесьTтип возвращаемого значения, который вы хотите. один из нихSupplier<T>простой функциональный интерфейс.

1CompletableFuture<String> hasReturn = CompletableFuture.supplyAsync(new Supplier<String>() {
2    @Override
3    public String get() {
4        return "hasReturn";
5    }
6});

можно использовать в это времяlambdaВыражение делает приведенную выше логику более понятной

1CompletableFuture<String> hasReturnLambda = CompletableFuture.supplyAsync(TestFuture::get);
2
3private static String get() {
4    return "hasReturnLambda";
5}

Получить возвращаемое значение

Асинхронные задачи также имеют возвращаемые значения.Когда мы хотим использовать возвращаемые значения асинхронных задач, мы можем вызватьCompletableFutureизget()Блокируется до тех пор, пока не будет выполнена асинхронная задача и не будет возвращено возвращаемое значение.

Ставим вышеперечисленноеget()Измените метод, чтобы сделать его паузу на десять секунд.

 1private static String get() {
2    System.out.println("Begin Invoke getFuntureHasReturnLambda");
3    try {
4        Thread.sleep(10000);
5    } catch (InterruptedException e) {
6
7    }
8    System.out.println("End Invoke getFuntureHasReturnLambda");
9    return "hasReturnLambda";
10}

тогда позвони

1public static void main(String[] args) throws ExecutionException, InterruptedException {
2    CompletableFuture<String> funtureHasReturnLambda = (CompletableFuture<String>) getFuntureHasReturnLambda();
3    System.out.println("Main Method Is Invoking");
4    funtureHasReturnLambda.get();
5    System.out.println("Main Method End");
6}

Видно, что вывод следующий, только вызовget()метод заблокирует текущий поток.

1Main Method Is Invoking
2Begin Invoke getFuntureHasReturnLambda
3End Invoke getFuntureHasReturnLambda
4Main Method End

пользовательское возвращаемое значение

Помимо ожидания возвращаемого значения асинхронной задачи, мы также можем в любой момент вызватьcomplete()метод для настройки возвращаемого значения.

 1CompletableFuture<String> funtureHasReturnLambda = (CompletableFuture<String>) getFuntureHasReturnLambda();
2System.out.println("Main Method Is Invoking");
3new Thread(()->{
4    System.out.println("Thread Is Invoking ");
5    try {
6        Thread.sleep(1000);
7        funtureHasReturnLambda.complete("custome value");
8    } catch (InterruptedException e) {
9        e.printStackTrace();
10    }
11    System.out.println("Thread End ");
12}).run();
13String value = funtureHasReturnLambda.get();
14System.out.println("Main Method End value is "+ value);

Мы можем обнаружить, что вывод является выходным значением только что запущенного потока.Конечно, это потому, что наш асинхронный метод настроен на ожидание 10 секунд.Если асинхронный метод ждет 1 секунду, а вновь запущенный поток ждет 10 секунд , то выходным значением является асинхронный метод.значение в .

1Main Method Is Invoking
2Begin Invoke getFuntureHasReturnLambda
3Thread Is Invoking 
4Thread End 
5Main Method End value is custome value

Последовательное выполнение асинхронных задач

Если завершение асинхронной задачи зависит от завершения предыдущей асинхронной задачи, как это записать? это вызовget()Получает ли метод возвращаемое значение, а затем выполняет его? Как-то неудобно так писать.CompletableFutureПредоставляет нам способ выполнить наше требование о последовательном выполнении некоторых асинхронных задач.thenApply,thenAccept,thenRunэти три метода. Разница между этими тремя методами.

имя метода Получать ли возвращаемое значение предыдущей задачи Есть ли возвращаемое значение
thenApply может получить имеют
thenAccept может получить никто
thenRun недоступно никто

Итак, в целомthenAccept,thenRunЭти два метода используются в самом конце цепочки вызовов. Далее давайте прочувствуем это на реальном примере.

 1//thenApply  可获取到前一个任务的返回值,也有返回值
2CompletableFuture<String> seqFutureOne = CompletableFuture.supplyAsync(()-> "seqFutureOne");
3CompletableFuture<String> seqFutureTwo = seqFutureOne.thenApply(name -> name + " seqFutureTwo");
4System.out.println(seqFutureTwo.get());
5
6
7//thenAccept  可获取到前一个任务的返回值,但是无返回值
8CompletableFuture<Void> thenAccept = seqFutureOne
9        .thenAccept(name -> System.out.println(name + "thenAccept"));
10System.out.println("-------------");
11System.out.println(thenAccept.get());
12
13//thenRun 获取不到前一个任务的返回值,也无返回值
14System.out.println("-------------");
15CompletableFuture<Void> thenRun = seqFutureOne.thenRun(() -> {
16    System.out.println("thenRun");
17});
18System.out.println(thenRun.get());

Возвращаемая информация выглядит следующим образом

1seqFutureOne seqFutureTwo
2seqFutureOnethenAccept
3-------------
4null
5-------------
6thenRun
7null

Разница между thenApply и thenApplyAsync

Мы можем обнаружить, что все три метода имеют суффиксAsyncметод, такой какthenApplyAsync. Так сAsyncВ чем разница между методом и методом без этого суффикса? мы будем использоватьthenApplyиthenApplyAsyncДва метода сравниваются, и другие такие же, как этот.

Разница между этими двумя методами заключается в том, кто будет выполнять эту задачу.thenApplyAsync, то поток выполнения изForkJoinPool.commonPool()Получите другой поток для выполнения, если вы используетеthenApply,еслиsupplyAsyncМетод выполняется очень быстро, затемthenApplyЗадача выполняется основным потоком, и если выполнение особенно медленное,supplyAsyncТо же, что и поток выполнения. Далее, давайте рассмотрим пример, используяsleepспособ реагироватьsupplyAsyncСкорость исполнения.

 1//thenApply和thenApplyAsync的区别
2System.out.println("-------------");
3CompletableFuture<String> supplyAsyncWithSleep = CompletableFuture.supplyAsync(()->{
4    try {
5        Thread.sleep(10000);
6    } catch (InterruptedException e) {
7        e.printStackTrace();
8    }
9    return "supplyAsyncWithSleep Thread Id : " + Thread.currentThread();
10});
11CompletableFuture<String> thenApply = supplyAsyncWithSleep
12        .thenApply(name -> name + "------thenApply Thread Id : " + Thread.currentThread());
13CompletableFuture<String> thenApplyAsync = supplyAsyncWithSleep
14        .thenApplyAsync(name -> name + "------thenApplyAsync Thread Id : " + Thread.currentThread());
15System.out.println("Main Thread Id: "+ Thread.currentThread());
16System.out.println(thenApply.get());
17System.out.println(thenApplyAsync.get());
18System.out.println("-------------No Sleep");
19CompletableFuture<String> supplyAsyncNoSleep = CompletableFuture.supplyAsync(()->{
20    return "supplyAsyncNoSleep Thread Id : " + Thread.currentThread();
21});
22CompletableFuture<String> thenApplyNoSleep = supplyAsyncNoSleep
23        .thenApply(name -> name + "------thenApply Thread Id : " + Thread.currentThread());
24CompletableFuture<String> thenApplyAsyncNoSleep = supplyAsyncNoSleep
25        .thenApplyAsync(name -> name + "------thenApplyAsync Thread Id : " + Thread.currentThread());
26System.out.println("Main Thread Id: "+ Thread.currentThread());
27System.out.println(thenApplyNoSleep.get());
28System.out.println(thenApplyAsyncNoSleep.get());

Мы видим, что выход

1-------------
2Main Thread Id: Thread[main,5,main]
3supplyAsyncWithSleep Thread Id : Thread[ForkJoinPool.commonPool-worker-1,5,main]------thenApply Thread Id : Thread[ForkJoinPool.commonPool-worker-1,5,main]
4supplyAsyncWithSleep Thread Id : Thread[ForkJoinPool.commonPool-worker-1,5,main]------thenApplyAsync Thread Id : Thread[ForkJoinPool.commonPool-worker-1,5,main]
5-------------No Sleep
6Main Thread Id: Thread[main,5,main]
7supplyAsyncNoSleep Thread Id : Thread[ForkJoinPool.commonPool-worker-2,5,main]------thenApply Thread Id : Thread[main,5,main]
8supplyAsyncNoSleep Thread Id : Thread[ForkJoinPool.commonPool-worker-2,5,main]------thenApplyAsync Thread Id : Thread[ForkJoinPool.commonPool-worker-2,5,main]

можно увидетьsupplyAsyncЕсли метод выполняется медленноthenApplyпоток выполнения метода иsupplyAsyncПоток выполнения тот же, еслиsupplyAsyncЕсли метод выполняется быстро, тоthenApplyпоток выполнения метода иMainПоток выполнения метода тот же.

Объединение CompletableFutures

поставить дваCompletableFutureЕсть два способа совмещения

  1. thenCompose(): вторая операция будет выполнена только после завершения первой задачи.
  2. thenCombine(): сделать что-то, когда обе асинхронные задачи выполнены

использование thenCompose()

Мы определяем две асинхронные задачи, предполагая, что вторая задача по времени должна использовать возвращаемое значение первой задачи по времени.

1public static CompletableFuture<String> getTastOne(){
2    return CompletableFuture.supplyAsync(()-> "topOne");
3}
4
5public static CompletableFuture<String> getTastTwo(String s){
6    return CompletableFuture.supplyAsync(()-> s + "  topTwo");
7}

мы используемthenCompose()способ написать

1CompletableFuture<String> thenComposeComplet = getTastOne().thenCompose(s -> getTastTwo(s));
2System.out.println(thenComposeComplet.get());

выход

1topOne  topTwo

Если вы помните предыдущийthenApply()метод, вы должны подумать об этом использованииthenApply()метод также может выполнять аналогичную функцию.

1//thenApply
2CompletableFuture<CompletableFuture<String>> thenApply = getTastOne()
3        .thenApply(s -> getTastTwo(s));
4System.out.println(thenApply.get().get());

Но мы обнаружили, что возвращаемое значение является типом вложенного возврата, и нам нужно дважды вызвать, чтобы получить окончательное возвращаемое значение.get()

использование thenCombine()

Например, нам нужно вычислить сумму возвращаемых значений двух асинхронных методов. Операция суммирования может быть рассчитана только в том случае, если получено значение двух асинхронных методов, поэтому мы можем использоватьthenCombine()метод расчета.

1CompletableFuture<Integer> thenComposeOne = CompletableFuture.supplyAsync(() -> 192);
2CompletableFuture<Integer> thenComposeTwo = CompletableFuture.supplyAsync(() -> 196);
3CompletableFuture<Integer> thenComposeCount = thenComposeOne
4        .thenCombine(thenComposeTwo, (s, y) -> s + y);
5System.out.println(thenComposeCount.get());

В настоящее времяthenComposeOneиthenComposeTwoОн будет вызван, когда все будет завершено и передано вthenCombineфункция обратного вызова метода.

Объединение нескольких CompletableFuture

выше мы используемthenCompose()иthenCombine()Два метода объединяют дваCompletableFutureв сборе, если мы хотим объединить любое количествоCompletableFutureКомбинация?Для комбинирования можно использовать следующие два метода.

  • allOf(): ждать всехCompletableFutureФункция обратного вызова будет запущена после завершения
  • anyOf(): только один из нихCompletableFutureзавершено, то функция обратного вызова будет выполнена. Обратите внимание, что другие задачи в это время выполняться не будут.

Далее мы продемонстрируем использование двух методов

 1//allOf()
2CompletableFuture<Integer> one = CompletableFuture.supplyAsync(() -> 1);
3CompletableFuture<Integer> two = CompletableFuture.supplyAsync(() -> 2);
4CompletableFuture<Integer> three = CompletableFuture.supplyAsync(() -> 3);
5CompletableFuture<Integer> four = CompletableFuture.supplyAsync(() -> 4);
6CompletableFuture<Integer> five = CompletableFuture.supplyAsync(() -> 5);
7CompletableFuture<Integer> six = CompletableFuture.supplyAsync(() -> 6);
8
9CompletableFuture<Void> voidCompletableFuture = CompletableFuture.allOf(one, two, three, four, five, six);
10voidCompletableFuture.thenApply(v->{
11    return Stream.of(one,two,three,four, five, six)
12            .map(CompletableFuture::join)
13            .collect(Collectors.toList());
14}).thenAccept(System.out::println);
15
16CompletableFuture<Void> voidCompletableFuture1 = CompletableFuture.runAsync(() -> {
17    try {
18        Thread.sleep(1000);
19    } catch (Exception e) {
20
21    }
22    System.out.println("1");
23});

Мы определяем 6CompletableFutureждать всехCompletableFutureДождитесь завершения всех задач, а затем выведите его значение.

anyOf()использование

 1CompletableFuture<Void> voidCompletableFuture1 = CompletableFuture.runAsync(() -> {
2try {
3    Thread.sleep(1000);
4} catch (Exception e) {
5
6}
7System.out.println("voidCompletableFuture1");
8});
9
10CompletableFuture<Void> voidCompletableFutur2 = CompletableFuture.runAsync(() -> {
11try {
12    Thread.sleep(2000);
13} catch (Exception e) {
14
15}
16System.out.println("voidCompletableFutur2");
17});
18
19CompletableFuture<Void> voidCompletableFuture3 = CompletableFuture.runAsync(() -> {
20try {
21    Thread.sleep(3000);
22} catch (Exception e) {
23
24}
25System.out.println("voidCompletableFuture3");
26});
27
28CompletableFuture<Object> objectCompletableFuture = CompletableFuture
29    .anyOf(voidCompletableFuture1, voidCompletableFutur2, voidCompletableFuture3);
30objectCompletableFuture.get();

Здесь мы определяем 3CompletableFutureВыполните несколько трудоемких задач, на этот раз в первую очередьCompletableFutureОн будет завершен первым. Результаты печати следующие.

1voidCompletableFuture1

Обработка исключений

мы понимаемCompletableFutureКак выполнять асинхронно, как комбинировать разныеCompletableFuture, как выполнить последовательноCompletableFuture. Затем следует важный шаг: что делать, если при выполнении асинхронной задачи возникает исключение. Сначала напишем пример.

1CompletableFuture.supplyAsync(()->{
2    //发生异常
3    int i = 10/0;
4    return "Success";
5}).thenRun(()-> System.out.println("thenRun"))
6.thenAccept(v -> System.out.println("thenAccept"));
7
8CompletableFuture.runAsync(()-> System.out.println("CompletableFuture.runAsync"));

В результате выполнения мы обнаружили, что пока в одной из цепочек выполнения есть исключение, следующая цепочка не будет выполняться, но и остальные цепочки под основным процессом не будут выполняться.CompletableFutureвсе еще будет работать.

1CompletableFuture.runAsync

exceptionally()

мы можем использоватьexceptionallyОбработка исключений

 1//处理异常
2
3CompletableFuture<String> exceptionally = CompletableFuture.supplyAsync(() -> {
4    //发生异常
5    int i = 10 / 0;
6    return "Success";
7}).exceptionally(e -> {
8    System.out.println(e);
9    return "Exception has Handl";
10});
11System.out.println(exceptionally.get());

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

1java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
2Exception has Handl

handle()

перечислитьhandle()Метод также может перехватывать исключения и настраивать возвращаемое значение.exceptionally()Метод отличаетсяhandle()Метод будет вызываться независимо от того, возникнет ли исключение. Примеры следующие

 1System.out.println("-------有异常-------");
2CompletableFuture.supplyAsync(()->{
3    //发生异常
4    int i = 10/0;
5    return "Success";
6}).handle((response,e)->{
7    System.out.println("Exception:" + e);
8    System.out.println("Response:" + response);
9    return response;
10});
11
12System.out.println("-------无异常-------");
13CompletableFuture.supplyAsync(()->{
14    return "Sucess";
15}).handle((response,e)->{
16    System.out.println("Exception:" + e);
17    System.out.println("Response:" + response);
18    return response;
19});

Печать выглядит следующим образом, мы видим, что когда исключение не возникаетhandle()метод также называется

1-------有异常-------
2Exception:java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
3Response:null
4-------无异常-------
5Exception:null
6Response:Sucess

адрес исходного кода

Справочная статья