掃二維碼與項目經(jīng)理溝通
我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
本文轉(zhuǎn)載自微信公眾號「月伴飛魚」,作者日常加油站。轉(zhuǎn)載本文請聯(lián)系月伴飛魚公眾號。

我們提供的服務有:成都網(wǎng)站設計、成都網(wǎng)站制作、外貿(mào)網(wǎng)站建設、微信公眾號開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認證、韶山ssl等。為上千企事業(yè)單位解決了網(wǎng)站和推廣的問題。提供周到的售前咨詢和貼心的售后服務,是有科學管理、有技術(shù)的韶山網(wǎng)站制作公司
最近看公司代碼,多線程編程用的比較多,其中有對CompletableFuture的使用,所以想寫篇文章總結(jié)下
在日常的Java8項目開發(fā)中,CompletableFuture是很強大的并行開發(fā)工具,其語法貼近java8的語法風格,與stream一起使用也能大大增加代碼的簡潔性
大家可以多應用到工作中,提升接口性能,優(yōu)化代碼
CompletableFuture是Java 8新增的一個類,用于異步編程,繼承了Future和CompletionStage
這個Future主要具備對請求結(jié)果獨立處理的功能,CompletionStage用于實現(xiàn)流式處理,實現(xiàn)異步請求的各個階段組合或鏈式處理,因此completableFuture能實現(xiàn)整個異步調(diào)用接口的扁平化和流式處理,解決原有Future處理一系列鏈式異步請求時的復雜編碼
Future的局限性
1、Future 的結(jié)果在非阻塞的情況下,不能執(zhí)行更進一步的操作
我們知道,使用Future時只能通過isDone()方法判斷任務是否完成,或者通過get()方法阻塞線程等待結(jié)果返回,它不能非阻塞的情況下,執(zhí)行更進一步的操作。
2、不能組合多個Future的結(jié)果
假設你有多個Future異步任務,你希望最快的任務執(zhí)行完時,或者所有任務都執(zhí)行完后,進行一些其他操作
3、多個Future不能組成鏈式調(diào)用
當異步任務之間有依賴關(guān)系時,F(xiàn)uture不能將一個任務的結(jié)果傳給另一個異步任務,多個Future無法創(chuàng)建鏈式的工作流。
4、沒有異常處理
現(xiàn)在使用CompletableFuture能幫助我們完成上面的事情,讓我們編寫更強大、更優(yōu)雅的異步程序
創(chuàng)建異步任務
通常可以使用下面幾個CompletableFuture的靜態(tài)方法創(chuàng)建一個異步任務
- public static CompletableFuture
runAsync(Runnable runnable); //創(chuàng)建無返回值的異步任務 - public static CompletableFuture
runAsync(Runnable runnable, Executor executor); //無返回值,可指定線程池(默認使用ForkJoinPool.commonPool) - public static CompletableFuture supplyAsync(Supplier supplier); //創(chuàng)建有返回值的異步任務
- public static CompletableFuture supplyAsync(Supplier supplier, Executor executor); //有返回值,可指定線程池
使用示例:
- Executor executor = Executors.newFixedThreadPool(10);
- CompletableFuture
future = CompletableFuture.runAsync(() -> { - //do something
- }, executor);
- int poiId = 111;
- CompletableFuture
future = CompletableFuture.supplyAsync(() -> { - PoiDTO poi = poiService.loadById(poiId);
- return poi.getName();
- });
- // Block and get the result of the Future
- String poiName = future.get();
使用回調(diào)方法
通過future.get()方法獲取異步任務的結(jié)果,還是會阻塞的等待任務完成
CompletableFuture提供了幾個回調(diào)方法,可以不阻塞主線程,在異步任務完成后自動執(zhí)行回調(diào)方法中的代碼
- public CompletableFuture
thenRun(Runnable runnable); //無參數(shù)、無返回值 - public CompletableFuture
thenAccept(Consumer super T> action); //接受參數(shù),無返回值 - public CompletableFuture thenApply(Function super T,? extends U> fn); //接受參數(shù)T,有返回值U
使用示例:
- CompletableFuture
future = CompletableFuture.supplyAsync(() -> "Hello") - .thenRun(() -> System.out.println("do other things. 比如異步打印日志或發(fā)送消息"));
- //如果只想在一個CompletableFuture任務執(zhí)行完后,進行一些后續(xù)的處理,不需要返回值,那么可以用thenRun回調(diào)方法來完成。
- //如果主線程不依賴thenRun中的代碼執(zhí)行完成,也不需要使用get()方法阻塞主線程。
- CompletableFuture
future = CompletableFuture.supplyAsync(() -> "Hello") - .thenAccept((s) -> System.out.println(s + " world"));
- //輸出:Hello world
- //回調(diào)方法希望使用異步任務的結(jié)果,并不需要返回值,那么可以使用thenAccept方法
- CompletableFuture
future = CompletableFuture.supplyAsync(() -> { - PoiDTO poi = poiService.loadById(poiId);
- return poi.getMainCategory();
- }).thenApply((s) -> isMainPoi(s)); // boolean isMainPoi(int poiId);
- future.get();
- //希望將異步任務的結(jié)果做進一步處理,并需要返回值,則使用thenApply方法。
- //如果主線程要獲取回調(diào)方法的返回,還是要用get()方法阻塞得到
組合兩個異步任務
- //thenCompose方法中的異步任務依賴調(diào)用該方法的異步任務
- public CompletableFuture thenCompose(Function super T, ? extends CompletionStage> fn);
- //用于兩個獨立的異步任務都完成的時候
- public CompletableFuture
thenCombine(CompletionStage extends U> other, - BiFunction super T,? super U,? extends V> fn);
使用示例:
- CompletableFuture
> poiFuture = CompletableFuture.supplyAsync(
- () -> poiService.queryPoiIds(cityId, poiId)
- );
- //第二個任務是返回CompletableFuture的異步方法
- CompletableFuture
> getDeal(List
poiIds){ - return CompletableFuture.supplyAsync(() -> poiService.queryPoiIds(poiIds));
- }
- //thenCompose
- CompletableFuture
> resultFuture = poiFuture.thenCompose(poiIds -> getDeal(poiIds));
- resultFuture.get();
thenCompose和thenApply的功能類似,兩者區(qū)別在于thenCompose接受一個返回CompletableFuture的Function,當想從回調(diào)方法返回的CompletableFuture中直接獲取結(jié)果U時,就用thenCompose
如果使用thenApply,返回結(jié)果resultFuture的類型是CompletableFuture>>,而不是CompletableFuture>
- CompletableFuture
future = CompletableFuture.supplyAsync(() -> "Hello") - .thenCombine(CompletableFuture.supplyAsync(() -> "world"), (s1, s2) -> s1 + s2);
- //future.get()
組合多個CompletableFuture
當需要多個異步任務都完成時,再進行后續(xù)處理,可以使用allOf方法
- CompletableFuture
poiIDTOFuture = CompletableFuture - .supplyAsync(() -> poiService.loadPoi(poiId))
- .thenAccept(poi -> {
- model.setModelTitle(poi.getShopName());
- //do more thing
- });
- CompletableFuture
productFuture = CompletableFuture - .supplyAsync(() -> productService.findAllByPoiIdOrderByUpdateTimeDesc(poiId))
- .thenAccept(list -> {
- model.setDefaultCount(list.size());
- model.setMoreDesc("more");
- });
- //future3等更多異步任務,這里就不一一寫出來了
- CompletableFuture.allOf(poiIDTOFuture, productFuture, future3, ...).join(); //allOf組合所有異步任務,并使用join獲取結(jié)果
該方法挺適合C端的業(yè)務,比如通過poiId異步的從多個服務拿門店信息,然后組裝成自己需要的模型,最后所有門店信息都填充完后返回
這里使用了join方法獲取結(jié)果,它和get方法一樣阻塞的等待任務完成
多個異步任務有任意一個完成時就返回結(jié)果,可以使用anyOf方法
- CompletableFuture
future1 = CompletableFuture.supplyAsync(() -> { - try {
- TimeUnit.SECONDS.sleep(2);
- } catch (InterruptedException e) {
- throw new IllegalStateException(e);
- }
- return "Result of Future 1";
- });
- CompletableFuture
future2 = CompletableFuture.supplyAsync(() -> { - try {
- TimeUnit.SECONDS.sleep(1);
- } catch (InterruptedException e) {
- throw new IllegalStateException(e);
- }
- return "Result of Future 2";
- });
- CompletableFuture
future3 = CompletableFuture.supplyAsync(() -> { - try {
- TimeUnit.SECONDS.sleep(3);
- } catch (InterruptedException e) {
- throw new IllegalStateException(e);
- return "Result of Future 3";
- });
- CompletableFuture
- System.out.println(anyOfFuture.get()); // Result of Future 2
- Integer age = -1;
- CompletableFuture
maturityFuture = CompletableFuture.supplyAsync(() -> { - if(age < 0) {
- throw new IllegalArgumentException("Age can not be negative");
- }
- if(age > 18) {
- return "Adult";
- } else {
- return "Child";
- }
- }).exceptionally(ex -> {
- System.out.println("Oops! We have an exception - " + ex.getMessage());
- return "Unknown!";
- }).thenAccept(s -> System.out.print(s));
- //Unkown!
exceptionally方法可以處理異步任務的異常,在出現(xiàn)異常時,給異步任務鏈一個從錯誤中恢復的機會,可以在這里記錄異?;蚍祷匾粋€默認值
使用handler方法也可以處理異常,并且無論是否發(fā)生異常它都會被調(diào)用
- Integer age = -1;
- CompletableFuture
maturityFuture = CompletableFuture.supplyAsync(() -> { - if(age < 0) {
- throw new IllegalArgumentException("Age can not be negative");
- }
- if(age > 18) {
- return "Adult";
- } else {
- return "Child";
- }
- }).handle((res, ex) -> {
- if(ex != null) {
- System.out.println("Oops! We have an exception - " + ex.getMessage());
- return "Unknown!";
- }
- return res;
- });
分片和并行處理:分片借助stream實現(xiàn),然后通過CompletableFuture實現(xiàn)并行執(zhí)行,最后做數(shù)據(jù)聚合(其實也是stream的方法)
CompletableFuture并不提供單獨的分片api,但可以借助stream的分片聚合功能實現(xiàn)
舉個例子:
- //請求商品數(shù)量過多時,做分批異步處理
- List
> skuBaseIdsList = ListUtils.partition(skuIdList, 10);//分片
- //并行
- List
>> futureList = Lists.newArrayList(); - for (List
skuId : skuBaseIdsList) { - CompletableFuture
> tmpFuture = getSkuSales(skuId);
- futureList.add(tmpFuture);
- }
- //聚合
- futureList.stream().map(CompletalbleFuture::join).collent(Collectors.toList());
帶大家領略下CompletableFuture異步編程的優(yōu)勢
這里我們用CompletableFuture實現(xiàn)水泡茶程序
首先還是需要先完成分工方案,在下面的程序中,我們分了3個任務:
下面是代碼實現(xiàn),你先略過runAsync()、supplyAsync()、thenCombine()這些不太熟悉的方法,從大局上看,你會發(fā)現(xiàn):
- //任務1:洗水壺->燒開水
- CompletableFuture f1 =
- CompletableFuture.runAsync(()->{
- System.out.println("T1:洗水壺...");
- sleep(1, TimeUnit.SECONDS);
- System.out.println("T1:燒開水...");
- sleep(15, TimeUnit.SECONDS);
- });
- //任務2:洗茶壺->洗茶杯->拿茶葉
- CompletableFuture f2 =
- CompletableFuture.supplyAsync(()->{
- System.out.println("T2:洗茶壺...");
- sleep(1, TimeUnit.SECONDS);
- System.out.println("T2:洗茶杯...");
- sleep(2, TimeUnit.SECONDS);
- System.out.println("T2:拿茶葉...");
- sleep(1, TimeUnit.SECONDS);
- return "龍井";
- });
- //任務3:任務1和任務2完成后執(zhí)行:泡茶
- CompletableFuture f3 =
- f1.thenCombine(f2, (__, tf)->{
- System.out.println("T1:拿到茶葉:" + tf);
- System.out.println("T1:泡茶...");
- return "上茶:" + tf;
- });
- //等待任務3執(zhí)行結(jié)果
- System.out.println(f3.join());
- void sleep(int t, TimeUnit u) {
- try {
- u.sleep(t);
- }catch(InterruptedException e){}
- }
1.CompletableFuture默認線程池是否滿足使用
前面提到創(chuàng)建CompletableFuture異步任務的靜態(tài)方法runAsync和supplyAsync等,可以指定使用的線程池,不指定則用CompletableFuture的默認線程池
- private static final Executor asyncPool = useCommonPool ?
- ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
可以看到,CompletableFuture默認線程池是調(diào)用ForkJoinPool的commonPool()方法創(chuàng)建,這個默認線程池的核心線程數(shù)量根據(jù)CPU核數(shù)而定,公式為Runtime.getRuntime().availableProcessors() - 1,以4核雙槽CPU為例,核心線程數(shù)量就是4*2-1=7個
這樣的設置滿足CPU密集型的應用,但對于業(yè)務都是IO密集型的應用來說,是有風險的,當qps較高時,線程數(shù)量可能就設的太少了,會導致線上故障
所以可以根據(jù)業(yè)務情況自定義線程池使用
2.get設置超時時間不能串行g(shù)et,不然會導致接口延時線程數(shù)量*超時時間

我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流