1. 程式人生 > 程式設計 >Java8——非同步程式設計

Java8——非同步程式設計

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>() {
@Override
3    public String get() {
4        return "hasReturn";
5    }
6});
複製程式碼

此時可以使用lambda表示式使上面的邏輯更加清晰

String> hasReturnLambda = CompletableFuture.supplyAsync(TestFuture::get);
2
3private static 4    "hasReturnLambda";
5}
複製程式碼

獲取返回值

非同步任務也是有返回值的,當我們想要用到非同步任務的返回值時,我們可以呼叫CompletableFutureget()阻塞,直到有非同步任務執行完有返回值才往下執行。

我們將上面的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."End Invoke getFuntureHasReturnLambda");
9    10}
複製程式碼

然後進行呼叫

1public static void main(String[] args) throws ExecutionException, InterruptedException {
2    CompletableFuture<String> funtureHasReturnLambda = (CompletableFuture<String>) getFuntureHasReturnLambda();
3    System."Main Method Is Invoking");
4    funtureHasReturnLambda.get();
5    System."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. 3new Thread(()->{
4    System."Thread Is Invoking ");
5     6        Thread.sleep(1000);
7        funtureHasReturnLambda.complete("custome value");
8    }  9        e.printStackTrace();
10    }
11    System."Thread End ");
12}).run();
13String value = funtureHasReturnLambda.14System."Main Method End value is "value);
複製程式碼

我們可以發現輸出是新起執行緒的輸出值,當然這是因為我們的非同步方法設定了等待10秒,如果此時非同步方法等待1秒,新起的執行緒等待10秒,那麼輸出的值就是非同步方法中的值了。

Thread Is Invoking 
End 
5End value is custome value
複製程式碼

按順序執行非同步任務

如果有一個非同步任務的完成需要依賴前一個非同步任務的完成,那麼該如何寫呢?是呼叫get()方法獲得返回值以後然後再執行嗎?這樣寫有些麻煩,CompletableFuture為我們提供了方法來完成我們想要順序執行一些非同步任務的需求。thenApplythenAcceptthenRun這三個方法。這三個方法的區別就是。

thenRun
方法名 是否可獲得前一個任務的返回值 是否有返回值
thenApply 能獲得
thenAccept
不可獲得

所以一般來說thenRun這兩個方法在呼叫鏈的最末端使用。接下來我們用真實的例子感受一下。

 1//thenApply  可獲取到前一個任務的返回值,也有返回值
2CompletableFuture<String> seqFutureOne = CompletableFuture.supplyAsync(()-> "seqFutureOne");
3CompletableFuture<String> seqFutureTwo = seqFutureOne.thenApply(name -> name + " seqFutureTwo");
4System.out.println(seqFutureTwo.get());
5
7//thenAccept  可獲取到前一個任務的返回值,但是無返回值
8CompletableFuture<Void> thenAccept = seqFutureOne
9        .thenAccept(name -> System.out.println(name + "thenAccept"));
10System."-------------");
11System.out.println(thenAccept.12
13//thenRun 獲取不到前一個任務的返回值,也無返回值
15CompletableFuture<Void> thenRun = seqFutureOne.thenRun(() -> {
16    System."thenRun");
17});
18System.out.println(thenRun.get());
複製程式碼

返回的資訊如下

1seqFutureOne seqFutureTwo
2seqFutureOnethenAccept
3-------------
4null
5-------------
6thenRun
7null
複製程式碼

thenApply和thenApplyAsync的區別

我們可以發現這三個方法都帶有一個字尾為Async的方法,例如thenApplyAsync。那麼帶Async的方法和不帶此字尾的方法有什麼不同呢?我們就以thenApplythenApplyAsync兩個方法進行對比,其他的和這個一樣的。

這兩個方法區別就在於誰去執行這個任務,如果使用thenApplyAsync,那麼執行的執行緒是從ForkJoinPool.commonPool()中獲取不同的執行緒進行執行,如果使用thenApply,如果supplyAsync方法執行速度特別快,那麼thenApply任務就是主執行緒進行執行,如果執行特別慢的話就是和supplyAsync執行執行緒一樣。接下來我們通過例子來看一下,使用sleep方法來反應supplyAsync執行速度的快慢。

//thenApply和thenApplyAsync的區別
3CompletableFuture<String> supplyAsyncWithSleep = CompletableFuture.supplyAsync(()->{
4     5        Thread.sleep( 6    }  7        e.printStackTrace();
8    }
"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."Main Thread Id: "+ Thread.currentThread());
16System.out.println(thenApply.17System.out.println(thenApplyAsync."-------------No Sleep");
19CompletableFuture<String> supplyAsyncNoSleep = CompletableFuture.supplyAsync(()->{
20    "supplyAsyncNoSleep Thread Id : " + Thread.currentThread();
21});
22CompletableFuture<String> thenApplyNoSleep = supplyAsyncNoSleep
23        .thenApply(name -> name + 24CompletableFuture<String> thenApplyAsyncNoSleep = supplyAsyncNoSleep
25        .thenApplyAsync(name -> name + 26System.27System.out.println(thenApplyNoSleep.28System.out.println(thenApplyAsyncNoSleep.get());
複製程式碼

我們可以看到輸出為

1-------------
Main Thread IdThread[main,5,main]
supplyAsyncWithSleep Id : [ForkJoinPool.commonPool-worker-1,main]------thenApply ------thenApplyAsync -------------No Sleep
67supplyAsyncNoSleep [ForkJoinPool.commonPool-worker-2,155); padding-right: 20px; word-spacing: 0px; word-wrap: inherit !important; word-break: inherit !important;" class="linenum hljs-number">8複製程式碼

可以看到supplyAsync方法執行速度慢的話thenApply方法執行執行緒和supplyAsync執行執行緒相同,如果supplyAsync方法執行速度快的話,那麼Main方法執行執行緒相同。

組合CompletableFuture

將兩個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    ()-> s + "  topTwo");
7}
複製程式碼

我們利用thenCompose()方法進行編寫

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

輸出就是

topOne  topTwo
複製程式碼

如果還記得前面的thenApply()方法的話,應該會想這個利用thenApply()方法也是能夠實現類似的功能的。

1//thenApply
2CompletableFuture<CompletableFuture<String>> thenApply = getTastOne()
3        .thenApply(s -> getTastTwo(s));
4System.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());
複製程式碼

此時thenComposeOnethenComposeTwo都完成時才會呼叫傳給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, six)
12            .map(CompletableFuture::join)
13            .collect(Collectors.toList());
14}).thenAccept(System.out::println);
15
16CompletableFuture<Void> voidCompletableFuture1 = CompletableFuture.runAsync(() -> {
17    18        Thread.sleep(19    } catch (Exception e) {
20
21    }
22    System.out.println("1");
23});
複製程式碼

我們定義了6個CompletableFuture等待所有的CompletableFuture等待所有任務完成以後然後將其值輸出。

anyOf()的用法

 1CompletableFuture<Void> voidCompletableFuture1 = CompletableFuture.runAsync( 2 3    Thread.sleep( 4 6}
7System.out.println("voidCompletableFuture1");
8});
9
10CompletableFuture<Void> voidCompletableFutur2 = CompletableFuture.runAsync(1112    Thread.sleep(2000);
1314
15}
16System.out.println("voidCompletableFutur2");
18
19CompletableFuture<Void> voidCompletableFuture3 = CompletableFuture.runAsync(2021    Thread.sleep(3000);
2223
24}
25System.out.println("voidCompletableFuture3");
26});
27
28CompletableFuture<Object> objectCompletableFuture = CompletableFuture
29    .anyOf(voidCompletableFuture1, voidCompletableFutur2, voidCompletableFuture3);
30objectCompletableFuture.get();
複製程式碼

這裡我們定義了3個CompletableFuture進行一些耗時的任務,此時第一個CompletableFuture會率先完成。列印結果如下。

voidCompletableFuture1
複製程式碼

異常處理

我們瞭解了CompletableFuture如何非同步執行,如何組合不同的CompletableFuture,如何順序執行CompletableFuture。那麼接下來還有一個重要的一步,就是在執行非同步任務時發生異常的話該怎麼辦。我們先寫個例子。

1CompletableFuture.supplyAsync(//發生異常
3    int i = 10/0;
"Success";
5}).thenRun(()-> System.out.println("thenRun"))
6.thenAccept(v -> System.out.println(7
8CompletableFuture.runAsync("CompletableFuture.runAsync"));
複製程式碼

執行結果為,我們發現只要執行鏈中有一個發生了異常,那麼接下來的鏈條也就不執行了,但是主流程下的其他CompletableFuture還是會執行的。

CompletableFuture.runAsync
複製程式碼

exceptionally()

我們可以使用exceptionally進行異常的處理

//處理異常
2
3CompletableFuture<String> exceptionally = CompletableFuture.supplyAsync(() -> {
4    //發生異常
5    int i = 10 /  6     7}).exceptionally(e -> {
out.println(e);
"Exception has Handl";
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 =  6}).handle((response,e)->{
7    System.out.println("Exception:" + e);
8    System.out.println("Response:" + response);
return response;
11
12System.out.println("-------無異常-------");
13CompletableFuture.supplyAsync(14    "Sucess";
15}).handle(16    System.out.println(17    System.out.println(18    19});
複製程式碼

列印如下,我們可以看到在沒有發生異常的時候handle()方法也被呼叫了

1-------有異常-------
2Exception:java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
3Response:null
4-------無異常-------
5Exception:null
6Response:Sucess
複製程式碼

原始碼地址

參考文章