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
Есть два способа совмещения
-
thenCompose()
: вторая операция будет выполнена только после завершения первой задачи. -
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