Java中的CompletableFuture詳解
1.Future
1.1 Future接口理論知識復習
Future接口(FutueTask實現(xiàn)類)定義了操作異步任務執(zhí)行一些方法,如獲取異步任務的執(zhí)行結果、取消任務的執(zhí)行、判斷任務是否被取消、判斷任務執(zhí)行是否完畢等。 比如主線程讓一個子線程去執(zhí)行任務,子線程可能比較耗時,啟動子線程開始執(zhí)行任務后, 主線程就去做其他事情了,忙其它事情或者先執(zhí)行完,過了一會才去獲取子任務的執(zhí)行結果或變更的任務狀態(tài)。
Future接口可以為主線程開一個分支任務,專門為主線程處理耗時和費力的復雜業(yè)務。
Future接口能干什么?
Future是Java5新加的一個接口,它提供了一種異步并行計算的功能。 如果主線程需要執(zhí)行一個很耗時的計算任務,我們就可以通過future把這個任務放到異步線程中執(zhí)行。主線程繼續(xù)處理其他任務或者先行結束,再通過Future獲取計算結果。
代碼說話: Runnable接口Callable接口
Future接口和FutureTask實現(xiàn)類
目的:異步多線程任務執(zhí)行且返回有結果,三個特點:多線程/有返回/異步任務
1.2 FutureTask架構

綠色虛線:表示實現(xiàn)的關系,實現(xiàn)一個接口 綠色實線:表示接口之間的繼承 藍色實線:表示類之間的繼承
1.3 Future編碼實戰(zhàn)
public class FutureTaskTest {
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
ExecutorService executorService = Executors.newFixedThreadPool(3);
long start = System.currentTimeMillis();
FutureTask<String> task2 = new FutureTask<>(() -> {
TimeUnit.SECONDS.sleep(2);
return "2";
});
FutureTask<String> task1 = new FutureTask<>(() -> {
TimeUnit.SECONDS.sleep(1);
return "1";
});
FutureTask<String> task3 = new FutureTask<>(() -> {
TimeUnit.SECONDS.sleep(3);
return "3";
});
executorService.submit(task1);
executorService.submit(task2);
executorService.submit(task3);
System.out.println(task1.get());
System.out.println(task2.get(3,TimeUnit.SECONDS));
while (true){
if(task3.isDone()){
System.out.println(task3.get());
break;
}else {
TimeUnit.MILLISECONDS.sleep(200);
}
}
System.out.println("執(zhí)行耗時:"+(System.currentTimeMillis()-start));
executorService.shutdown();
}
}1
2
3
執(zhí)行耗時:3066
優(yōu)缺點分析
優(yōu)點
- future+線程池異步多線程任務配合,能顯著提高程序的執(zhí)行效率。
缺點
- 一旦調用get()方法求結果,如果計算沒有完成容易導致程序阻塞 isDone()輪詢的方式會耗費無謂的CPU資源,而且也不見得能及時地得到計算結果. 如果想要異步獲取結果,通常都會以輪詢的方式去獲取結果盡量不要阻塞
- Future對于結果的獲取不是很友好,只能通過阻塞或輪詢的方式得到任務的結果。
- 將多個異步任務的計算結果組合起來,后一個異步任務的計算結果需要前一個異步任務的值
- 將兩個或多個異步計算合成一個異步計算,這幾個異步計算互相獨立,同時后面這個又依賴前一個處理的結果。
- 對計算速度選最快:當Future集合中某個任務最快結束時,返回結果,返回第一名處理
對于簡單的業(yè)務場景使用Future完全OK,但想完成上述一些復雜的任務,使用Future之前提供的那點API就囊中羞澀,處理起來不夠優(yōu)雅,這時候還是讓CompletableFuture以聲明式的方式優(yōu)雅的處理這些需求。Future能干的,CompletableFuture都能干。
2. CompletableFuture
2.1 CompletableFuture對Future的改進
CompletableFuture異步線程發(fā)生異常,不會影響主線程,用來記錄日志特別方便。
CompletableFuture為什么出現(xiàn) get()方法在Future 計算完成之前會一直處在阻塞狀態(tài)下,isDone()方法容易耗費CPU資源, 對于真正的異步處理我們希望是可以通過傳入回調函數(shù),在Future結束時自動調用該回調函數(shù),這樣,我們就不用等待結果。
阻塞的方式和異步編程的設計理念相違背,而輪詢的方式會耗費無謂的CPU資源。因此,JDK8設計出CompletableFuture。 CompletableFuture提供了一種觀察者模式類似的機制,可以讓任務執(zhí)行完成后通知監(jiān)聽的一方。
CompletableFuture和CompletionStage

CompletionStage CompletionStage代表異步計算過程中的某一個階段,一個階段完成以后可能會觸發(fā)另外一個階段 一個階段的計算執(zhí)行可以是一個Function, Consumer或者Runnable。比如: stage.thenApply(x -> square(x)).thenAccept(×->System.out.print(x)).thenRun(( ->systeh.out.println()) 一個階段的執(zhí)行可能是被單個階段的完成觸發(fā),也可能是由多個階段一起觸發(fā) 代表異步計算過程中的某一個階段,一個階段完成以后可能會觸發(fā)另外一個階段,有些類似Linux系統(tǒng)的管道分隔符傳參數(shù)。
CompletableFuture 在Java8中,CompletableFuture提供了非常強大的Future的擴展功能,可以幫助我們簡化異步編程的復雜性,并且提供了函數(shù)式編程的能力,可以通過回調的方式處理計算結果,也提供了轉換和組合CompletableFuture 的方法。 它可能代表一個明確完成的Future,也有可能代表一個完成階段(CompletionStage ),它支持在計算完成以后觸發(fā)一些函數(shù)或執(zhí)行某些動作。 它實現(xiàn)了Future和CompletionStage接口
核心的四個靜態(tài)方法,來創(chuàng)建一個異步任務

從Java8開始引入了CompletableFuture,它是Future的功能增強版。減少阻塞和輪詢,可以傳入回調對象,當異步任務完成或者發(fā)生異常時,自動調用回調對象的回調方法
CompletableFuture的優(yōu)點 異步任務結束時,會自動回調某個對象的方法; 主線程設置好回調后,不再關心異步任務的執(zhí)行,異步任務之間可以順序執(zhí)行 異步任務出錯時,會自動回調某個對象的方法;
2.2 案例精講
函數(shù)式編程已經主流 先說說join和get對比 說說你過去工作中的項目亮點?大廠業(yè)務需求說明 一波流Java8函數(shù)式編程帶走-比價案例實戰(zhàn)
Lambda表達式+Stream流式調用+Chain鏈式調用+Java8函數(shù)式編程

案例精講-從電商網站的比價需求講起
需求說明 同一款產品,同時搜索出同款產品在各大電商平臺的售價;
輸出返回: 出來結果希望是同款產品的在不同地方的價格清單列表,返回一個List《mysql》in jd price is 88.05 《mysql》in dangdang price is 86.11 《mysql》in taobao price is 90.43
解決方案,比對同一個商品在各個平臺上的價格,要求獲得一個清單列表,
1 )step by step,按部就班,查完京東查淘寶,查完淘寶查天貓
2 )all in,萬箭齊發(fā),一口氣多線程異步任務同時查詢
public class CompletableFutureDemo {
static List<NetMall> list = Arrays.asList(
new NetMall("vip"),
new NetMall("jd"),
new NetMall("tb"),
new NetMall("pdd")
);
public static void main(String[] args) {
long cur1 = System.currentTimeMillis();
getPrice("Phone").forEach(r-> System.out.println(r));
System.out.println("getPrice耗時"+(System.currentTimeMillis()-cur1));
long cur2 = System.currentTimeMillis();
getPriceByCompletableFuture("Phone").forEach(r-> System.out.println(r));
System.out.println("getPriceByCompletableFuture耗時"+(System.currentTimeMillis()-cur2));
}
private static List<String> getPrice(String productName){
return list.stream()
.map(r->String.format(productName+" in %s price is %.2f",r.getName(),r.calcPrice(productName)))
.collect(Collectors.toList());
}
private static List<String> getPriceByCompletableFuture(String productName){
return list.stream()
.map(r-> CompletableFuture.supplyAsync(()->String.format(productName+" in %s price is %.2f",r.getName(),r.calcPrice(productName))))
.collect(Collectors.toList())
.stream()
.map(s->s.join())
.collect(Collectors.toList());
}
}
class NetMall{
private String name;
public double calcPrice(String productName){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return ThreadLocalRandom.current().nextDouble(100000000)+productName.hashCode();
}
public NetMall(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}2.3 CompletableFuture常用方法
2.3.1 獲得結果和觸發(fā)計算
獲得結果
public T get() 不見不散 public T get(long timeout,TimeUnit unit) 過時不候 public T join() public T getNow(T valuelfAbsent)
沒有計算完成的情況下,給我一個替代結果。計算完,返回計算完成后的結果。立即獲取結果不阻賽。沒算完,返回設定 的valuelfAbsent值
主動觸發(fā)計算 public bgolean complete(T value) 是否打斷get方法立即返回括號值
public class CompletableFutureTest {
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "hello CompletableFuture";
});
System.out.println(completableFuture.getNow("心急吃不了熱豆腐"));
System.out.println(completableFuture.get());
System.out.println(completableFuture.get(1500, TimeUnit.MILLISECONDS));
System.out.println(completableFuture.join());
System.out.println(completableFuture.complete("未雨綢繆")+"\t"+completableFuture.join());
}
}2.3.2 對計算結果進行處理
thenApply 計算結果存在依賴關系,這兩個線程串行化 異常相關:由于存在依賴關系(當前步錯,不走下一步),當前步驟有異常的話就叫停。
public class CompletableFutureTest2 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 6;
},executorService).thenApply((r)-> {
int i=2/0;
return r * 5;
}).thenApply((r)-> {
System.out.println(r);
return r - 2;
}).whenComplete((v, e) -> {
System.out.println("計算結果:"+v);
}).exceptionally(e -> {
System.out.println(e.getMessage());
System.out.println(e);
return null;
});
System.out.println("============主線程==========");
executorService.shutdown();
}
}發(fā)生異常后進入exceptionally代碼塊,但是thenApply中的代碼不會執(zhí)行,whenComplete依舊會執(zhí)行
============主線程==========
計算結果:null
java.lang.ArithmeticException: / by zero
java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
handle
計算結果存在依賴關系,這兩個線程串行化 異常相關:有異常也可以往下一步走,根據(jù)帶的異常參數(shù)可以進步處理
發(fā)生異常后進入exceptionally代碼塊,但是handle和whenComplete依舊會執(zhí)行
============主線程==========
null
計算結果:null
java.lang.NullPointerException
java.util.concurrent.CompletionException: java.lang.NullPointerException

2.3.3 對計算結果進行消費
接收任務的處理結果,并消費處理,無返回結果thenAccept
public class CompletableFutureTest3 {
public static void main(String[] args) {
CompletableFuture.supplyAsync(()->{
return 3;
}).thenApply(r->{
return r*8;
}).thenApply(r->{
return r/2;
}).thenAccept(r-> System.out.println(r));
System.out.println(CompletableFuture.supplyAsync(()->"6666").thenRun(()->{}).join());
System.out.println(CompletableFuture.supplyAsync(()->"6666").thenAccept(r-> System.out.println(r)).join());
System.out.println(CompletableFuture.supplyAsync(()->"6666").thenApply(r->r+"9999").join());
}
}12
null
6666
null
66669999
completableFuture和線程池說明

以thenRun和thenRunAsync為例,有什么區(qū)別?
沒有傳入自定義線程池,都用默認線程池ForkJoinPool; 傳入了一個自定義線程池, 如果你執(zhí)行第一個任務的時候,傳入了一個自定義線程池: 調用thenRun方法執(zhí)行第二個任務時,則第二個任務和第一個任務是共用同一個線程池。 調用thenRunAsync執(zhí)行第二個任務時,則第一個任務使用的是你自己傳入的線程池,第二個任務使用的是ForkJoin線程池
有可能處理太快,系統(tǒng)優(yōu)化切換原則,直接使用main線程處理 其它如: thenAccept和thenAcceptAsync,thenApply和thenApplyAsync等,它們之間的區(qū)別也是同理
2.3.4 對計算速度進行選用與對計算結果進行合并
applyToEither:誰快用誰 thenCombine:兩個completionStage任務都完成后,最終能把兩個任務的結果一起交給thenCombine來處理。先完成的先等著,等待其它分支任務
public class CompletableFutureTest4 {
public static void main(String[] args) {
CompletableFuture<String> first = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "1號選手";
});
CompletableFuture<String> second = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "2號選手";
});
CompletableFuture<String> result = first.applyToEither(second, r -> r + "is winner");
CompletableFuture<String> res = first.thenCombine(second, (x, y) -> x + y);
System.out.println(result.join());
System.out.println(res.join());
}
}1號選手is winner
1號選手2號選手
到此這篇關于Java中的CompletableFuture詳解的文章就介紹到這了,更多相關CompletableFuture詳解內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
java靈活使用mysql中json類型字段存儲數(shù)據(jù)詳解
在數(shù)據(jù)庫設計中,面對一對多的關系,如訂單和商品,可以考慮使用單表存儲而非傳統(tǒng)的分表方式,這篇文章主要介紹了java靈活使用mysql中json類型字段存儲數(shù)據(jù)的相關資料,需要的朋友可以參考下2024-09-09
SpringBoot+jpa配置如何根據(jù)實體類自動創(chuàng)建表
這篇文章主要介紹了SpringBoot+jpa配置如何根據(jù)實體類自動創(chuàng)建表,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11
SpringBoot整合kaptcha驗證碼過程(復制粘貼即可用)
本文介紹了如何在Spring Boot項目中整合Kaptcha驗證碼實現(xiàn),通過配置和編寫相應的Controller、工具類以及前端頁面,可以生成和驗證驗證碼,文中指出了在項目結構上的注意事項,避免因包結構問題導致驗證碼無法顯示2025-01-01
Spring Data JPA 建立表的聯(lián)合主鍵
這篇文章主要介紹了Spring Data JPA 建立表的聯(lián)合主鍵。本文詳細的介紹了2種方式,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-04-04
IDEA使用Maven創(chuàng)建module出現(xiàn)Ignored?pom.xml問題及解決
這篇文章主要介紹了IDEA使用Maven創(chuàng)建module出現(xiàn)Ignored?pom.xml問題及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-11-11

