分享一波:程序員賺外快-必看的巔峰干貨
對于Node開發者來說,非阻塞異步編程是他們引以為傲的地方。而在JDK8中,也引入了非阻塞異步編程的概念。所謂非阻塞異步編程,就是一種不需要等待返回結果的多線程的回調方法的封裝。使用非阻塞異步編程,可以很大程度上解決高并發場景的各種問題,提高程序的運行效率。
為什么要使用非阻塞異步編程
在jdk8之前,我們使用java的多線程編程,一般是通過Runnable中的run方法進行的。這種方法有個明顯的缺點:沒有返回值。這時候,大家會使用Callable+Future的方式去實現,代碼如下。
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future stringFuture = executor.submit(new Callable() {
@Override
public String call() throws Exception {
Thread.sleep(2000);
return “async thread”;
}
});
Thread.sleep(1000);
System.out.println(“main thread”);
System.out.println(stringFuture.get());
}
這無疑是對高并發訪問的一種緩沖方法。這種方式有一個致命的缺點就是阻塞式調用,當調用了get方法之后,會有大量的時間耗費在等待返回值之中。
不管怎么看,這種做法貌似都不太妥當,至少在代碼美觀性上就看起來很蛋疼。而且某些場景無法使用,比如:
多個異步線程執行時間可能不一致,我們的主線程不能一直等著。
兩個異步任務之間相互獨立,但是第二個依賴第一個的執行結果
在這種場景下,CompletableFuture的優勢就展現出來了 。同時,CompletableFuture的封裝中使用了函數式編程,這讓我們的代碼顯得更加簡潔、優雅。
不了解函數式編程的朋友,可以參考我之前的博客。JDK8新特性
CompletableFuture使用詳解
runAsync和supplyAsync方法
CompletableFuture提供了四個靜態方法來創建一個異步操作。
public static CompletableFuture runAsync(Runnable runnable)
public static CompletableFuture runAsync(Runnable runnable, Executor executor)
public static CompletableFuture supplyAsync(Supplier supplier)
public static CompletableFuture supplyAsync(Supplier supplier, Executor executor)
沒有指定Executor的方法會使用ForkJoinPool.commonPool() 作為它的線程池執行異步代碼。如果指定線程池,則使用指定的線程池運行。以下所有的方法都類同。
runAsync方法不支持返回值。
supplyAsync可以支持返回值。
代碼示例
/*** 無返回值** @throws Exception*/
@Test
public void testRunAsync() throws Exception {CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {try {TimeUnit.SECONDS.sleep(1);} catch (Exception ignored) {}System.out.println("run end ...");});future.get();
}/*** 有返回值** @throws Exception*/
@Test
public void testSupplyAsync() throws Exception {CompletableFuture<Long> future = CompletableFuture.supplyAsync(() -> {System.out.println("run end...");return System.currentTimeMillis();});Long time = future.get();System.out.println(time);
}
計算結果完成時的回調方法
當CompletableFuture的計算結果完成,或者拋出異常的時候,可以執行特定的操作。
public CompletableFuture whenComplete(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)
public CompletableFuture exceptionally(Function<Throwable,? extends T> fn)
這里需要說的一點是,whenComplete和whenCompleteAsync的區別。
whenComplete:使用執行當前任務的線程繼續執行whenComplete的任務。
whenCompleteAsync:使用新的線程執行任務。
exceptionally:執行出現異常時,走這個方法。
代碼示例
/*** 當CompletableFuture的計算結果完成,或者拋出異常的時候,可以執行特定的Action。* whenComplete:是執行當前任務的線程執行繼續執行 whenComplete 的任務。* whenCompleteAsync:是執行把 whenCompleteAsync 這個任務繼續提交給線程池來進行執行。* exceptionally:執行出現異常時,走這個方法** @throws Exception*/
@Test
public void testWhenComplete() throws Exception {CompletableFuture.runAsync(() -> {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("運行結束");}).whenComplete((t, action) -> {System.out.println("執行完成");}).exceptionally(t -> {System.out.println("出現異常:" + t.getMessage());return null;});TimeUnit.SECONDS.sleep(2);
}
thenApply
當一個線程依賴另一個線程時,可以使用thenApply方法把這兩個線程串行化,第二個任務依賴第一個任務的返回值。
代碼示例
/*** 當一個線程依賴另一個線程時,可以使用 thenApply 方法來把這兩個線程串行化。* 第二個任務依賴第一個任務的結果** @throws Exception*/
@Test
public void testThenApply() throws Exception {CompletableFuture<Long> future = CompletableFuture.supplyAsync(() -> {long result = new Random().nextInt();System.out.println("result:" + result);return result;}).thenApply(t -> {long result = t * 5;System.out.println("result2:" + result);return result;});Long result = future.get();System.out.println(result);
}
handle
handle是執行任務完成時對結果的處理。與thenApply方法處理方式基本一致,
不同的是,handle是在任務完成后執行,不管這個任務是否出現了異常,而thenApply只可以執行正常的任務,任務出現了異常則不執行。
代碼示例
/*** handle 是執行任務完成時對結果的處理。* handle 方法和 thenApply 方法處理方式基本一樣。* 不同的是 handle 是在任務完成后再執行,還可以處理異常的任務。* thenApply 只可以執行正常的任務,任務出現異常則不執行 thenApply 方法。** @throws Exception*/
@Test
public void testHandle() throws Exception {CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {int i = 10 / 0;return i;}).handle((p, t) -> {int result = -1;if (t == null) {result = p * 2;} else {System.out.println(t.getMessage());}return result;});System.out.println(future.get());
}
thenAccept
thenAccept用于接收任務的處理結果,并消費處理,無返回結果。
代碼示例
/*** 接收任務的處理結果,并消費處理,無返回結果。** @throws Exception*/
@Test
public void testThenAccept() throws Exception {CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {return new Random().nextInt();}).thenAccept(num -> {System.out.println(num);});System.out.println(future.get());
}
thenRun
上個任務執行完之后再執行thenRun的任務,二者只存在先后執行順序的關系,后者并不依賴前者的計算結果,同時,沒有返回值。
代碼示例
/*** 該方法同 thenAccept 方法類似。不同的是上個任務處理完成后,并不會把計算的結果傳給 thenRun 方法。* 只是處理玩任務后,執行 thenRun 的后續操作。** @throws Exception*/
@Test
public void testThenRun() throws Exception {CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {return new Random().nextInt();}).thenRun(() -> {System.out.println("進入了thenRun");});System.out.println(future.get());
}
thenCombine
thenCombine會把兩個CompletableFuture的任務都執行完成后,把兩個任務的返回值一塊交給thenCombine處理(有返回值)。
代碼示例
/*** thenCombine 會把 兩個 CompletableFuture 的任務都執行完成后* 把兩個任務的結果一塊交給 thenCombine 來處理。** @throws Exception*/
@Test
public void testThenCombine() throws Exception {CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {return "hello";}).thenCombine(CompletableFuture.supplyAsync(() -> {return "world";}), (t1, t2) -> {return t1 + " " + t2;});System.out.println(future.get());
}
thenAcceptBoth
當兩個CompletableFuture都執行完成后,把結果一塊交給thenAcceptBoth處理(無返回值)
代碼示例
/*** 當兩個 CompletableFuture 都執行完成后* 把結果一塊交給thenAcceptBoth來進行消耗** @throws Exception*/
@Test
public void testThenAcceptBoth() throws Exception {CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {return "hello";}).thenAcceptBoth(CompletableFuture.supplyAsync(() -> {return "world";}), (t1, t2) -> {System.out.println(t1 + " " + t2);});System.out.println(future.get());
}
applyToEither
兩個CompletableFuture,誰執行返回的結果快,就用哪個的結果進行下一步操作(有返回值)。
代碼示例
/*** 兩個CompletableFuture,誰執行返回的結果快,我就用那個CompletionStage的結果進行下一步的轉化操作** @throws Exception*/
@Test
public void testApplyToEither() throws Exception {CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {try {Thread.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}return "hello";}).applyToEither(CompletableFuture.supplyAsync(() -> {return "world";}), (t) -> {return t;});System.out.println(future.get());
}
acceptEither
兩個CompletableFuture,誰執行返回的結果快,就用哪個的結果進行下一步操作(無返回值)。
代碼示例
/*** 兩個CompletableFuture,誰執行返回的結果快,我就用那個CompletionStage的結果進行下一步的消耗操作。** @throws Exception*/
@Test
public void testAcceptEither() throws Exception {CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {return "hello";}).acceptEither(CompletableFuture.supplyAsync(() -> {return "world";}), t1 -> {System.out.println(t1);});System.out.println(future.get());
}
runAfterEither
兩個CompletableFuture,任何一個完成了都會執行下一步操作
代碼示例
/*** 兩個CompletableFuture,任何一個完成了都會執行下一步的操作** @throws Exception*/
@Test
public void testRunAfterEither() throws Exception {CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {return "hello";}).runAfterEither(CompletableFuture.supplyAsync(() -> {return "world";}), () -> {System.out.println("執行完了");});System.out.println(future.get());
}
runAfterBoth
兩個CompletableFuture,都完成了才會執行下一步操作。
代碼示例
/*** 兩個CompletableFuture,都完成了計算才會執行下一步的操作** @throws Exception*/
@Test
public void testRunAfterBoth() throws Exception {CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {return "hello";}).runAfterBoth(CompletableFuture.supplyAsync(() -> {return "world";}), () -> {System.out.println("執行完了");});System.out.println(future.get());
}
thenCompose
thenCompose方法允許對兩個CompletableFuture進行流水線操作,當第一個操作完成時,將其結果作為參數傳遞給第二個操作。
代碼示例
/*** thenCompose 方法允許你對兩個 CompletableFuture 進行流水線操作,* 第一個操作完成時,將其結果作為參數傳遞給第二個操作。* @throws Exception*/
@Test
public void testThenCompose() throws Exception {CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {int t = new Random().nextInt();System.out.println(t);return t;}).thenCompose(param -> {return CompletableFuture.supplyAsync(() -> {int t = param * 2;System.out.println("t2=" + t);return t;});});System.out.println(future.get());
}
結語
CompletableFuture是jdk8中新增的一個特性,特點是非阻塞異步編程。合理的使用非阻塞異步編程,比如將兩步關聯不大的操作并行處理,可以優化代碼的執行效率。同時,在高并發場景下,CompletableFuture也可以進行有效的性能優化。
*************************************優雅的分割線 **********************************
分享一波:程序員賺外快-必看的巔峰干貨
如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程
請關注微信公眾號:HB荷包
一個能讓你學習技術和賺錢方法的公眾號,持續更新