Java8 CompletableFuture 程式設計

JMCui發表於2019-08-14

一、簡介

 所謂非同步呼叫其實就是實現一個無需等待被呼叫函式的返回值而讓操作繼續執行的方法。在 Java 語言中,簡單的講就是另啟一個執行緒來完成呼叫中的部分計算,使呼叫繼續執行或返回,而不需要等待計算結果。但呼叫者仍需要取執行緒的計算結果。

 JDK5新增了 Future 介面,用於描述一個非同步計算的結果。雖然 Future 以及相關使用方法提供了非同步執行任務的能力,但是對於結果的獲取卻是很不方便,只能通過阻塞或者輪詢的方式得到任務的結果。阻塞的方式顯然和我們的非同步程式設計的初衷相違背,輪詢的方式又會耗費無謂的 CPU 資源,而且也不能及時地得到計算結果。

private static final ExecutorService POOL = Executors.newFixedThreadPool(TASK_THRESHOLD, new ThreadFactory() {
        AtomicInteger atomicInteger = new AtomicInteger(0);

        @Override
        public Thread newThread(Runnable r) {
            return new Thread(r, "demo15-" + atomicInteger.incrementAndGet());
        }
    });

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Future<Integer> submit = POOL.submit(() -> 123);
        // 1. get() 方法使用者返回計算結果,如果計算還沒有完成,則在get的時候會進行阻塞,直到獲取到結果為止
        Integer get = submit.get();
        // 2. isDone() 方法用於判斷當前Future是否執行完成。
        boolean done = submit.isDone();
        // 3. cancel(boolean mayInterruptIfRunning) 取消當前執行緒的執行。參數列示是否線上程執行的過程中阻斷。
        boolean cancel = submit.cancel(true);
        // 4. isCancelled() 判斷當前task是否被取消.
        boolean cancelled = submit.isCancelled();
        // 5. invokeAll 批量執行任務
        Callable<String> callable = () -> "Hello Future";
        List<Callable<String>> callables = Lists.newArrayList(callable, callable, callable, callable);
        List<Future<String>> futures = POOL.invokeAll(callables);
    }

 在Java8中,CompletableFuture 提供了非常強大的 Future 的擴充套件功能,可以幫助我們簡化非同步程式設計的複雜性,並且提供了函數語言程式設計的能力,可以通過回撥的方式處理計算結果,也提供了轉換和組合 CompletableFuture 的方法。

tips: CompletionStage 代表非同步計算過程中的某一個階段,一個階段完成以後可能會觸發另外一個階段。

二、CompletableFuture 使用

1. runAsync、supplyAsync

// 無返回值
public static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
// 有返回值
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

runAsync、supplyAsync 方法是 CompletableFuture 提供的建立非同步操作的方法。需要注意的是,如果沒有指定 Executor 作為執行緒池,將會使用ForkJoinPool.commonPool() 作為它的執行緒池執行非同步程式碼;如果指定執行緒池,則使用指定的執行緒池執行。以下所有的方法都類同。

public class Demo1 {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Void> runAsync = CompletableFuture.runAsync(() -> System.out.println(123));

        CompletableFuture<String> supplyAsync = CompletableFuture.supplyAsync(() -> "CompletableFuture");
        System.out.println(supplyAsync.get());
    }
}


2. whenComplete、exceptionally

// 執行完成時,當前任務的執行緒執行繼續執行 whenComplete 的任務。
public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action)
// 執行完成時,把 whenCompleteAsync 這個任務提交給執行緒池來進行執行。
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)
public CompletableFuture<T> exceptionally(Function<Throwable,? extends T> fn)

當 CompletableFuture 的計算完成時,會執行 whenComplete 方法;當 CompletableFuture 計算中丟擲異常時,會執行 exceptionally 方法。

public class Demo2 {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        CompletableFuture<Integer> runAsync = CompletableFuture.supplyAsync(() -> 123456);
        runAsync.whenComplete((t, throwable) -> {
            System.out.println(t);
            if (throwable != null) {
                throwable.printStackTrace();
            }
        });
        runAsync.whenCompleteAsync((t, throwable) -> {
            System.out.println(t);
            if (throwable != null) {
                throwable.printStackTrace();
            }
        });
        runAsync.exceptionally((throwable) -> {
            if (throwable != null) {
                throwable.printStackTrace();
            }
            return null;
        });
        TimeUnit.SECONDS.sleep(2);
    }
}


3. thenApply、handle

// T:上一個任務返回結果的型別
// U:當前任務的返回值型別

public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)

public <U> CompletionStage<U> handle(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn,Executor executor);

當一個執行緒依賴另一個執行緒時,可以使用 thenApply 方法來把這兩個執行緒序列化

handle 方法和 thenApply 方法處理方式基本一樣。不同的是 handle 是在任務完成後再執行,還可以處理異常的任務。thenApply 只可以執行正常的任務,任務出現異常則不執行 thenApply 方法。

public class Demo3 {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // thenApply
        CompletableFuture<Integer> thenApply = CompletableFuture.supplyAsync(() -> 123).thenApply(t -> t * t);
        System.out.println(thenApply.get());

       // handle
        CompletableFuture<Integer> handle = CompletableFuture.supplyAsync(() -> {
            int i = 10 / 0;
            return new Random().nextInt(10);
        }).handle((t, throwable) -> {
            if (throwable != null) {
                throwable.printStackTrace();
                return -1;
            }
            return t * t;
        });
        System.out.println(handle.get());
    }
}


4. thenAccept、thenRun

public CompletionStage<Void> thenAccept(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action,Executor executor);

public CompletionStage<Void> thenRun(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action,Executor executor);

thenAccept 接收任務的處理結果,並消費處理。無返回結果。

thenRun 跟 thenAccept 方法不一樣的是,不關心任務的處理結果。只要上面的任務執行完成,就開始執行 thenRun。

public class Demo4 {

    public static void main(String[] args) {
        // thenAccept
        CompletableFuture<Void> thenAccept = CompletableFuture.supplyAsync(() -> new Random().nextInt(10)).thenAccept(System.out::println);

       // thenRun
        CompletableFuture<Void> thenRun = CompletableFuture.supplyAsync(() -> new Random().nextInt(10)).thenRun(() -> System.out.println(123));
    }
}


5. thenCombine、thenAcceptBoth

 // T 表示第一個 CompletionStage 的返回結果型別
 // U 表示第二個 CompletionStage 的返回結果型別
 // V表示 thenCombine/thenAcceptBoth 處理結果型別
public <U,V> CompletionStage<V> thenCombine(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn,Executor executor);

public <U,V> CompletionStage<V> thenCombine(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn,Executor executor);

thenCombine、thenAcceptBoth 都是用來合併任務 —— 等待兩個 CompletionStage 的任務都執行完成後,把兩個任務的結果一併來處理。區別在於 thenCombine 有返回值;thenAcceptBoth 無返回值。

public class Demo5 {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // thenCombine
        CompletableFuture<String> thenCombine = CompletableFuture.supplyAsync(() -> new Random().nextInt(10))
                .thenCombine(CompletableFuture.supplyAsync(() -> "str"),
                        // 第一個引數是第一個 CompletionStage 的處理結果
                        // 第二個引數是第二個 CompletionStage 的處理結果
                        (i, s) -> i + s
                );
        System.out.println(thenCombine.get());

        // thenAcceptBoth 
        CompletableFuture<Void> thenAcceptBoth = CompletableFuture.supplyAsync(() -> new Random().nextInt(10))
                .thenAcceptBoth(CompletableFuture.supplyAsync(() -> "str"), 
                        (i, s) -> System.out.println(i + s));
    }
}


6. applyToEither、acceptEither、runAfterEither、runAfterBoth

  • applyToEither:兩個 CompletionStage,誰執行返回的結果快,就用那個 CompletionStage 的結果進行下一步的處理,有返回值。
  • acceptEither:兩個 CompletionStage,誰執行返回的結果快,就用那個 CompletionStage 的結果進行下一步的處理,無返回值。
  • runAfterEither:兩個 CompletionStage,任何一個完成了,都會執行下一步的操作(Runnable),無返回值。
  • runAfterBoth:兩個 CompletionStage,都完成了計算才會執行下一步的操作(Runnable),無返回值。

由於這幾個方法含義相近,使用更加類似,我們就以 applyToEither 來介紹...

// T 兩個 CompletionStage 組合運算後的結果型別
// U 下一步處理運算的結果返回值型別
public <U> CompletionStage<U> applyToEither(CompletionStage<? extends T> other,Function<? super T, U> fn);
public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,Function<? super T, U> fn);
public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,Function<? super T, U> fn,Executor executor);
public class Demo6 {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        CompletableFuture<Integer> applyToEither = CompletableFuture.supplyAsync(() -> {
            int nextInt = new Random().nextInt(10);
            try {
                Thread.sleep(nextInt);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("f1=" + nextInt);
            return nextInt;
        }).applyToEither(CompletableFuture.supplyAsync(() -> {
            int nextInt = new Random().nextInt(10);
            try {
                Thread.sleep(nextInt);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("f2=" + nextInt);
            return nextInt;
        }), i -> i);

        System.out.println(applyToEither.get());
    }
}


7. thenCompose

public <U> CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn);
public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn) ;
public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn, Executor executor) ;

thenCompose 方法允許你對兩個 CompletionStage 進行流水線操作,第一個操作完成時,將其結果作為引數傳遞給第二個操作。

public class Demo7 {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        CompletableFuture<Integer> thenCompose = CompletableFuture.supplyAsync(() -> new Random().nextInt(10))
                .thenCompose(i -> CompletableFuture.supplyAsync(() -> i * i));
        System.out.println(thenCompose.get());

    }
}



參考博文:https://www.jianshu.com/p/6bac52527ca4

相關文章