聊聊 JDK8 的 CompletableFuture ,你明白了嗎?
前段時(shí)間,阿粉已經(jīng)說(shuō)過(guò)一次CompletableFuture了,但是還是有讀者說(shuō),感覺(jué)不是很清晰,有點(diǎn)亂的樣子,今天阿粉就再來(lái)說(shuō)一下這個(gè)CompletableFuture的一些API的方法。
CompletableFuture
CompletableFuture是java.util.concurrent庫(kù)在java 8中新增的主要工具,同傳統(tǒng)的Future相比,其支持流式計(jì)算、函數(shù)式編程、完成通知、自定義異常處理等很多新的特性。
supplyAsync方法
通過(guò)該函數(shù)創(chuàng)建的CompletableFuture實(shí)例會(huì)異步執(zhí)行當(dāng)前傳入的計(jì)算任務(wù)。在調(diào)用端,則可以通過(guò)get或join獲取最終計(jì)算結(jié)果。
這個(gè)有兩個(gè)不同的實(shí)現(xiàn)方式,一種是我們傳入我們自己創(chuàng)建的線程池,然后使用我們創(chuàng)建的線程池進(jìn)行操作,還有一種就是不傳線程池,讓程序是使用默認(rèn)的線程池進(jìn)行操作。
//使用默認(rèn)線程池
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
return asyncSupplyStage(asyncPool, supplier);
}
//使用自定義線程池
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor) {
return asyncSupplyStage(screenExecutor(executor), supplier);
}
第一種只需傳入一個(gè)Supplier實(shí)例(一般使用lamda表達(dá)式),此時(shí)框架會(huì)默認(rèn)使用ForkJoin的線程池來(lái)執(zhí)行被提交的任務(wù)。
我們來(lái)自定義一個(gè)代碼看一下:
ExecutorService executors = Executors.newFixedThreadPool(5);
CompletableFuture<List<Order>> bFuture = CompletableFuture.supplyAsync(() -> {
//執(zhí)行查詢
QueryWrapper<Order> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("userId",params.getUserId());
queryWrapper.eq("SHOP_ID",params.getShopId());
List<Order> list = orderService.list(queryWrapper);
return list;
}, executors);
當(dāng)我們執(zhí)行查詢的時(shí)候,這時(shí)候?qū)嶋H上就屬于異步的查詢的,我們可以寫多個(gè)查詢,比如,上面的代碼我們查詢的是訂單,下面我們可以查詢用戶的信息,還是使用同樣的線程池。
CompletableFuture<List<User>> aFuture = CompletableFuture.supplyAsync(() -> {
//執(zhí)行查詢
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("SHOP_ID",params.getShopId());
List<User> list = userService.list(queryWrapper);
return list;
}, executors);
這時(shí)候,我們就可以獲取一下這個(gè)返回的結(jié)果,那么獲取返回結(jié)果我們需要調(diào)用什么方法呢?
可以通過(guò)get或join獲取最終計(jì)算結(jié)果。這時(shí)候我們可以打印一下,獲取結(jié)果的長(zhǎng)度。
List<User> list = aFuture.get();
List<Order> list = bFuture.get();
這樣,我們就可以使用異步查詢來(lái)完成我們對(duì)結(jié)果集的查詢。
這就有小伙伴想問(wèn),如果我想要用第一個(gè)的結(jié)果,去在第二個(gè)數(shù)據(jù)中去查詢,我們應(yīng)該怎么做呢?
thenApply
thenApply提交的任務(wù)類型需遵從Function簽名,也就是有入?yún)⒑头祷刂担渲腥雲(yún)榍爸萌蝿?wù)的結(jié)果。
什么意思呢?其實(shí)很簡(jiǎn)單,我們這里直接看代碼:
CompletableFuture<List<Order>> cFuture = bFuture.thenApply((list) -> {
List<String> collect = list.stream().map(User::getUserId).collect(Collectors.toList());
...
return orderList;
});
這實(shí)際上,就是我們根據(jù)查詢出的所有用戶的集合,直接獲取到他的userId,然后我們根據(jù)UserId,把這些用戶下的訂單數(shù)據(jù)都提取出來(lái),當(dāng)然,在實(shí)際使用中,我們理論上可以無(wú)限連接后續(xù)計(jì)算任務(wù),從而實(shí)現(xiàn)鏈條更長(zhǎng)的流式計(jì)算。
但是這種鏈?zhǔn)揭膊皇嵌挤浅5暮糜?,畢竟要控制住線程池,大家記得在使用完成之后,可以把自己創(chuàng)建的線程池小回調(diào),調(diào)用shutDown方法就可以了。我們?cè)俳又抡f(shuō)。
thenAccept
實(shí)際上thenAccept的效果,和thenApply 的效果等同,但是thenAccept提交的任務(wù)類型需遵從Consumer簽名,也就是有入?yún)⒌菦](méi)有返回值,其中入?yún)榍爸萌蝿?wù)的結(jié)果。
實(shí)際上調(diào)用的是和之前一樣的,但是就是沒(méi)有返回值了。
CompletableFuture<void> cFuture = bFuture.thenAccept((list) -> {
List<String> collect = list.stream().map(User::getUserId).collect(Collectors.toList());
...
});
這就是區(qū)別,不需要進(jìn)行返回了,這種一般用的地方比較特殊,就比如說(shuō)我們執(zhí)行了很多操作,但是在其中需要夾雜一些比如說(shuō)給某個(gè)服務(wù)進(jìn)行通知,但是我們并不需要這個(gè)服務(wù)給我們返回值的結(jié)果的時(shí)候,實(shí)際上完全可以使用這種方式。方便還快捷。當(dāng)然,你想直接調(diào)用,阿粉也是沒(méi)有意見的。
whenComplete
whenComplete主要用于注入任務(wù)完成時(shí)的回調(diào)通知邏輯。這個(gè)解決了傳統(tǒng)future在任務(wù)完成時(shí),無(wú)法主動(dòng)發(fā)起通知的問(wèn)題。前置任務(wù)會(huì)將計(jì)算結(jié)果或者拋出的異常作為入?yún)鬟f給回調(diào)通知函數(shù)。
這個(gè)方法就是相當(dāng)于是把前一個(gè)任務(wù)中的結(jié)果,通過(guò)第二個(gè)方法獲取結(jié)果,也并不會(huì)影響第二個(gè)的邏輯。
示例代碼如下:
CompletableFuture<List<Order>> listCompletableFuture = bFuture.whenComplete((r, e) -> {
if (e != null) {
System.out.println("compute failed!");
} else {
System.out.println("received result is " + r);
}
});
實(shí)際上最后答應(yīng)的就是接受者的信息。大家學(xué)會(huì)了么?