Java中的CompletableFuture詳解
1.Future
1.1 Future接口理論知識(shí)復(fù)習(xí)
Future接口(FutueTask實(shí)現(xiàn)類)定義了操作異步任務(wù)執(zhí)行一些方法,如獲取異步任務(wù)的執(zhí)行結(jié)果、取消任務(wù)的執(zhí)行、判斷任務(wù)是否被取消、判斷任務(wù)執(zhí)行是否完畢等。 比如主線程讓一個(gè)子線程去執(zhí)行任務(wù),子線程可能比較耗時(shí),啟動(dòng)子線程開(kāi)始執(zhí)行任務(wù)后, 主線程就去做其他事情了,忙其它事情或者先執(zhí)行完,過(guò)了一會(huì)才去獲取子任務(wù)的執(zhí)行結(jié)果或變更的任務(wù)狀態(tài)。
Future接口可以為主線程開(kāi)一個(gè)分支任務(wù),專門為主線程處理耗時(shí)和費(fèi)力的復(fù)雜業(yè)務(wù)。
Future接口能干什么?
Future是Java5新加的一個(gè)接口,它提供了一種異步并行計(jì)算的功能。 如果主線程需要執(zhí)行一個(gè)很耗時(shí)的計(jì)算任務(wù),我們就可以通過(guò)future把這個(gè)任務(wù)放到異步線程中執(zhí)行。主線程繼續(xù)處理其他任務(wù)或者先行結(jié)束,再通過(guò)Future獲取計(jì)算結(jié)果。
代碼說(shuō)話: Runnable接口Callable接口
Future接口和FutureTask實(shí)現(xiàn)類
目的:異步多線程任務(wù)執(zhí)行且返回有結(jié)果,三個(gè)特點(diǎn):多線程/有返回/異步任務(wù)
1.2 FutureTask架構(gòu)
綠色虛線:表示實(shí)現(xiàn)的關(guān)系,實(shí)現(xiàn)一個(gè)接口 綠色實(shí)線:表示接口之間的繼承 藍(lán)色實(shí)線:表示類之間的繼承
1.3 Future編碼實(shí)戰(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í)行耗時(shí):"+(System.currentTimeMillis()-start)); executorService.shutdown(); } }
1
2
3
執(zhí)行耗時(shí):3066
優(yōu)缺點(diǎn)分析
優(yōu)點(diǎn)
- future+線程池異步多線程任務(wù)配合,能顯著提高程序的執(zhí)行效率。
缺點(diǎn)
- 一旦調(diào)用get()方法求結(jié)果,如果計(jì)算沒(méi)有完成容易導(dǎo)致程序阻塞 isDone()輪詢的方式會(huì)耗費(fèi)無(wú)謂的CPU資源,而且也不見(jiàn)得能及時(shí)地得到計(jì)算結(jié)果. 如果想要異步獲取結(jié)果,通常都會(huì)以輪詢的方式去獲取結(jié)果盡量不要阻塞
- Future對(duì)于結(jié)果的獲取不是很友好,只能通過(guò)阻塞或輪詢的方式得到任務(wù)的結(jié)果。
- 將多個(gè)異步任務(wù)的計(jì)算結(jié)果組合起來(lái),后一個(gè)異步任務(wù)的計(jì)算結(jié)果需要前一個(gè)異步任務(wù)的值
- 將兩個(gè)或多個(gè)異步計(jì)算合成一個(gè)異步計(jì)算,這幾個(gè)異步計(jì)算互相獨(dú)立,同時(shí)后面這個(gè)又依賴前一個(gè)處理的結(jié)果。
- 對(duì)計(jì)算速度選最快:當(dāng)Future集合中某個(gè)任務(wù)最快結(jié)束時(shí),返回結(jié)果,返回第一名處理
對(duì)于簡(jiǎn)單的業(yè)務(wù)場(chǎng)景使用Future完全OK,但想完成上述一些復(fù)雜的任務(wù),使用Future之前提供的那點(diǎn)API就囊中羞澀,處理起來(lái)不夠優(yōu)雅,這時(shí)候還是讓CompletableFuture以聲明式的方式優(yōu)雅的處理這些需求。Future能干的,CompletableFuture都能干。
2. CompletableFuture
2.1 CompletableFuture對(duì)Future的改進(jìn)
CompletableFuture異步線程發(fā)生異常,不會(huì)影響主線程,用來(lái)記錄日志特別方便。
CompletableFuture為什么出現(xiàn) get()方法在Future 計(jì)算完成之前會(huì)一直處在阻塞狀態(tài)下,isDone()方法容易耗費(fèi)CPU資源, 對(duì)于真正的異步處理我們希望是可以通過(guò)傳入回調(diào)函數(shù),在Future結(jié)束時(shí)自動(dòng)調(diào)用該回調(diào)函數(shù),這樣,我們就不用等待結(jié)果。
阻塞的方式和異步編程的設(shè)計(jì)理念相違背,而輪詢的方式會(huì)耗費(fèi)無(wú)謂的CPU資源。因此,JDK8設(shè)計(jì)出CompletableFuture。 CompletableFuture提供了一種觀察者模式類似的機(jī)制,可以讓任務(wù)執(zhí)行完成后通知監(jiān)聽(tīng)的一方。
CompletableFuture和CompletionStage
CompletionStage CompletionStage代表異步計(jì)算過(guò)程中的某一個(gè)階段,一個(gè)階段完成以后可能會(huì)觸發(fā)另外一個(gè)階段 一個(gè)階段的計(jì)算執(zhí)行可以是一個(gè)Function, Consumer或者Runnable。比如: stage.thenApply(x -> square(x)).thenAccept(×->System.out.print(x)).thenRun(( ->systeh.out.println()) 一個(gè)階段的執(zhí)行可能是被單個(gè)階段的完成觸發(fā),也可能是由多個(gè)階段一起觸發(fā) 代表異步計(jì)算過(guò)程中的某一個(gè)階段,一個(gè)階段完成以后可能會(huì)觸發(fā)另外一個(gè)階段,有些類似Linux系統(tǒng)的管道分隔符傳參數(shù)。
CompletableFuture 在Java8中,CompletableFuture提供了非常強(qiáng)大的Future的擴(kuò)展功能,可以幫助我們簡(jiǎn)化異步編程的復(fù)雜性,并且提供了函數(shù)式編程的能力,可以通過(guò)回調(diào)的方式處理計(jì)算結(jié)果,也提供了轉(zhuǎn)換和組合CompletableFuture 的方法。 它可能代表一個(gè)明確完成的Future,也有可能代表一個(gè)完成階段(CompletionStage ),它支持在計(jì)算完成以后觸發(fā)一些函數(shù)或執(zhí)行某些動(dòng)作。 它實(shí)現(xiàn)了Future和CompletionStage接口
核心的四個(gè)靜態(tài)方法,來(lái)創(chuàng)建一個(gè)異步任務(wù)
從Java8開(kāi)始引入了CompletableFuture,它是Future的功能增強(qiáng)版。減少阻塞和輪詢,可以傳入回調(diào)對(duì)象,當(dāng)異步任務(wù)完成或者發(fā)生異常時(shí),自動(dòng)調(diào)用回調(diào)對(duì)象的回調(diào)方法
CompletableFuture的優(yōu)點(diǎn) 異步任務(wù)結(jié)束時(shí),會(huì)自動(dòng)回調(diào)某個(gè)對(duì)象的方法; 主線程設(shè)置好回調(diào)后,不再關(guān)心異步任務(wù)的執(zhí)行,異步任務(wù)之間可以順序執(zhí)行 異步任務(wù)出錯(cuò)時(shí),會(huì)自動(dòng)回調(diào)某個(gè)對(duì)象的方法;
2.2 案例精講
函數(shù)式編程已經(jīng)主流 先說(shuō)說(shuō)join和get對(duì)比 說(shuō)說(shuō)你過(guò)去工作中的項(xiàng)目亮點(diǎn)?大廠業(yè)務(wù)需求說(shuō)明 一波流Java8函數(shù)式編程帶走-比價(jià)案例實(shí)戰(zhàn)
Lambda表達(dá)式+Stream流式調(diào)用+Chain鏈?zhǔn)秸{(diào)用+Java8函數(shù)式編程
案例精講-從電商網(wǎng)站的比價(jià)需求講起
需求說(shuō)明 同一款產(chǎn)品,同時(shí)搜索出同款產(chǎn)品在各大電商平臺(tái)的售價(jià);
輸出返回: 出來(lái)結(jié)果希望是同款產(chǎn)品的在不同地方的價(jià)格清單列表,返回一個(gè)List《mysql》in jd price is 88.05 《mysql》in dangdang price is 86.11 《mysql》in taobao price is 90.43
解決方案,比對(duì)同一個(gè)商品在各個(gè)平臺(tái)上的價(jià)格,要求獲得一個(gè)清單列表,
1 )step by step,按部就班,查完京東查淘寶,查完淘寶查天貓
2 )all in,萬(wàn)箭齊發(fā),一口氣多線程異步任務(wù)同時(shí)查詢
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耗時(shí)"+(System.currentTimeMillis()-cur1)); long cur2 = System.currentTimeMillis(); getPriceByCompletableFuture("Phone").forEach(r-> System.out.println(r)); System.out.println("getPriceByCompletableFuture耗時(shí)"+(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 獲得結(jié)果和觸發(fā)計(jì)算
獲得結(jié)果
public T get() 不見(jiàn)不散 public T get(long timeout,TimeUnit unit) 過(guò)時(shí)不候 public T join() public T getNow(T valuelfAbsent)
沒(méi)有計(jì)算完成的情況下,給我一個(gè)替代結(jié)果。計(jì)算完,返回計(jì)算完成后的結(jié)果。立即獲取結(jié)果不阻賽。沒(méi)算完,返回設(shè)定 的valuelfAbsent值
主動(dòng)觸發(fā)計(jì)算 public bgolean complete(T value) 是否打斷get方法立即返回括號(hào)值
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 對(duì)計(jì)算結(jié)果進(jìn)行處理
thenApply 計(jì)算結(jié)果存在依賴關(guān)系,這兩個(gè)線程串行化 異常相關(guān):由于存在依賴關(guān)系(當(dāng)前步錯(cuò),不走下一步),當(dāng)前步驟有異常的話就叫停。
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("計(jì)算結(jié)果:"+v); }).exceptionally(e -> { System.out.println(e.getMessage()); System.out.println(e); return null; }); System.out.println("============主線程=========="); executorService.shutdown(); } }
發(fā)生異常后進(jìn)入exceptionally代碼塊,但是thenApply中的代碼不會(huì)執(zhí)行,whenComplete依舊會(huì)執(zhí)行
============主線程==========
計(jì)算結(jié)果:null
java.lang.ArithmeticException: / by zero
java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
handle
計(jì)算結(jié)果存在依賴關(guān)系,這兩個(gè)線程串行化 異常相關(guān):有異常也可以往下一步走,根據(jù)帶的異常參數(shù)可以進(jìn)步處理
發(fā)生異常后進(jìn)入exceptionally代碼塊,但是handle和whenComplete依舊會(huì)執(zhí)行
============主線程==========
null
計(jì)算結(jié)果:null
java.lang.NullPointerException
java.util.concurrent.CompletionException: java.lang.NullPointerException
2.3.3 對(duì)計(jì)算結(jié)果進(jìn)行消費(fèi)
接收任務(wù)的處理結(jié)果,并消費(fèi)處理,無(wú)返回結(jié)果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和線程池說(shuō)明
以thenRun和thenRunAsync為例,有什么區(qū)別?
沒(méi)有傳入自定義線程池,都用默認(rèn)線程池ForkJoinPool; 傳入了一個(gè)自定義線程池, 如果你執(zhí)行第一個(gè)任務(wù)的時(shí)候,傳入了一個(gè)自定義線程池: 調(diào)用thenRun方法執(zhí)行第二個(gè)任務(wù)時(shí),則第二個(gè)任務(wù)和第一個(gè)任務(wù)是共用同一個(gè)線程池。 調(diào)用thenRunAsync執(zhí)行第二個(gè)任務(wù)時(shí),則第一個(gè)任務(wù)使用的是你自己傳入的線程池,第二個(gè)任務(wù)使用的是ForkJoin線程池
有可能處理太快,系統(tǒng)優(yōu)化切換原則,直接使用main線程處理 其它如: thenAccept和thenAcceptAsync,thenApply和thenApplyAsync等,它們之間的區(qū)別也是同理
2.3.4 對(duì)計(jì)算速度進(jìn)行選用與對(duì)計(jì)算結(jié)果進(jìn)行合并
applyToEither:誰(shuí)快用誰(shuí) thenCombine:兩個(gè)completionStage任務(wù)都完成后,最終能把兩個(gè)任務(wù)的結(jié)果一起交給thenCombine來(lái)處理。先完成的先等著,等待其它分支任務(wù)
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號(hào)選手"; }); CompletableFuture<String> second = CompletableFuture.supplyAsync(() -> { try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } return "2號(hào)選手"; }); 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號(hào)選手is winner
1號(hào)選手2號(hào)選手
到此這篇關(guān)于Java中的CompletableFuture詳解的文章就介紹到這了,更多相關(guān)CompletableFuture詳解內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java靈活使用mysql中json類型字段存儲(chǔ)數(shù)據(jù)詳解
在數(shù)據(jù)庫(kù)設(shè)計(jì)中,面對(duì)一對(duì)多的關(guān)系,如訂單和商品,可以考慮使用單表存儲(chǔ)而非傳統(tǒng)的分表方式,這篇文章主要介紹了java靈活使用mysql中json類型字段存儲(chǔ)數(shù)據(jù)的相關(guān)資料,需要的朋友可以參考下2024-09-09關(guān)于Java8中map()和flatMap()的一些事
這篇文章主要給大家介紹了關(guān)于Java8中map()和flatMap()的一些事,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10關(guān)于RestTemplate中的Get請(qǐng)求
這篇文章主要介紹了關(guān)于RestTemplate中的Get請(qǐng)求,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07SpringBoot+jpa配置如何根據(jù)實(shí)體類自動(dòng)創(chuàng)建表
這篇文章主要介紹了SpringBoot+jpa配置如何根據(jù)實(shí)體類自動(dòng)創(chuàng)建表,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11SpringBoot整合kaptcha驗(yàn)證碼過(guò)程(復(fù)制粘貼即可用)
本文介紹了如何在Spring Boot項(xiàng)目中整合Kaptcha驗(yàn)證碼實(shí)現(xiàn),通過(guò)配置和編寫相應(yīng)的Controller、工具類以及前端頁(yè)面,可以生成和驗(yàn)證驗(yàn)證碼,文中指出了在項(xiàng)目結(jié)構(gòu)上的注意事項(xiàng),避免因包結(jié)構(gòu)問(wèn)題導(dǎo)致驗(yàn)證碼無(wú)法顯示2025-01-01Spring Data JPA 建立表的聯(lián)合主鍵
這篇文章主要介紹了Spring Data JPA 建立表的聯(lián)合主鍵。本文詳細(xì)的介紹了2種方式,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-04-04IDEA使用Maven創(chuàng)建module出現(xiàn)Ignored?pom.xml問(wèn)題及解決
這篇文章主要介紹了IDEA使用Maven創(chuàng)建module出現(xiàn)Ignored?pom.xml問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11基于Java實(shí)現(xiàn)的一層簡(jiǎn)單人工神經(jīng)網(wǎng)絡(luò)算法示例
這篇文章主要介紹了基于Java實(shí)現(xiàn)的一層簡(jiǎn)單人工神經(jīng)網(wǎng)絡(luò)算法,結(jié)合實(shí)例形式分析了java實(shí)現(xiàn)人工神經(jīng)網(wǎng)絡(luò)的具體實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-12-12