【詳解】Executors框架之CompleableFuture

引入

使用Future獲得異步執行結果時,要麼調用阻塞方法get(),要麼輪詢看isDone()是否爲true,這兩種方法都不是很好,因爲主線程也會被迫等待。

從Java 8開始引入了CompletableFuture,它針對Future做了改進,可以傳入回調對象,當異步任務完成或者發生異常時,自動調用回調對象的回調方法。

優勢如下

  • 可以利用結果進行級聯的執行
  • 會自動回調給調用者
  • 執行一批任務時,可以按照任務執行的順序,獲得結果
  • 可以並行的獲取結果,只拿最先獲取的結果級聯的執行

簡介

CompleableFuture依然是對Executor的封裝,看構造函數的源碼,可以知道一般情況下會創建一個ForkJoinPool,同時ThreadFactory會設置爲守護線程。這就意味着:一旦主線程結束,線程池就會關閉。

簡單實用

public class CompletableFutureTest {

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

        CompletableFuture.runAsync(()->{
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).whenComplete((v,t)->{
            System.out.println("Done");
        });
        Thread.currentThread().join();
    }
}

結果

Done

可以發現CompleableFuture,在執行完成後,會自動去調用下個任務的方法,不會受到阻塞。

創建CompleableFuture

創建CompleableFuture不建議使用構造方法,而是使用靜態的工廠方法構建。

public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)


public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)


public static <U> CompletableFuture<U> completedFuture(U value)


public static CompletableFuture<Void> runAsync(Runnable runnable,Executor executor)
public static CompletableFuture<Void> runAsync(Runnable runnable)

public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)                                             
  • allOf(CompletableFuture<?>... cfs):這個方法會返回一個全新的CompletableFuture,同時會把傳遞進去的所有CompletableFuture執行完纔算是執行完成

  • anyOf(CompletableFuture<?>... cfs):這個方法會返回一個全新的CompletableFuture,只要傳遞進去的一個CompletableFuture執行完,就算是執行完成

  • completedFuture(U value)可以假設一個執行出了一個結果,進行下面的級聯操作

  • runAsync:異步的執行Runnable

  • supplyAsync:異步的執行Supplier實例,會有回調的結果U

supplyAsync舉例

public class CompletableFutureTest {

    public static void main(String[] args) throws InterruptedException {
        supplyAsync();
        Thread.currentThread().join();
    }

    private static void supplyAsync() throws InterruptedException {

        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello ");

        CompletableFuture<String> future2 = future1.thenApplyAsync(obj -> {
            try {
                System.out.print(obj);
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "World! ";
        });

        future2.thenAccept(System.out::println);
    }

}

結果

Hello World! 

分析
需要給supplyAsync提供一個Supplier接口,如下所示:

public interface Supplier<T> {
    T get();
}

runAsync舉例

public class CompletableFutureTest {

    public static void main(String[] args) throws InterruptedException {
        supplyAsync();
        Thread.currentThread().join();
    }

    private static void supplyAsync() throws InterruptedException {

        CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> System.out.print("Hello "));

        CompletableFuture<Void> future2 = future1.thenAccept(t -> {
            System.out.println("World !");
        });
    }

}

結果

Hello World! 

分析
比一般的提交一個Runnable相比,可以更加靈活點使用,級聯、並聯等操作

anyOf 舉例

同時從新浪和網易查詢證券代碼,只要任意一個返回結果,就進行下一步查詢價格,查詢價格也同時從新浪和網易查詢,只要任意一個返回結果,就完成操作:

public class CompletableFutureTest {

    public static void main(String[] args) throws Exception {
        // 兩個CompletableFuture執行異步查詢:
        CompletableFuture<String> cfQueryFromSina = CompletableFuture.supplyAsync(() -> {
            return queryCode("中國石油", "https://finance.sina.com.cn/code/");
        });
        CompletableFuture<String> cfQueryFrom163 = CompletableFuture.supplyAsync(() -> {
            return queryCode("中國石油", "https://money.163.com/code/");
        });

        // 用anyOf合併爲一個新的CompletableFuture:
        CompletableFuture<Object> cfQuery = CompletableFuture.anyOf(cfQueryFromSina, cfQueryFrom163);

        // 兩個CompletableFuture執行異步查詢:
        CompletableFuture<Double> cfFetchFromSina = cfQuery.thenApplyAsync((code) -> {
            return fetchPrice((String) code, "https://finance.sina.com.cn/price/");
        });
        CompletableFuture<Double> cfFetchFrom163 = cfQuery.thenApplyAsync((code) -> {
            return fetchPrice((String) code, "https://money.163.com/price/");
        });

        // 用anyOf合併爲一個新的CompletableFuture:
        CompletableFuture<Object> cfFetch = CompletableFuture.anyOf(cfFetchFromSina, cfFetchFrom163);

        // 最終結果:
        cfFetch.thenAccept((result) -> {
            System.out.println("price: " + result);
        });
        // 主線程不要立刻結束,否則CompletableFuture默認使用的線程池會立刻關閉:
        Thread.sleep(200);
    }

    static String queryCode(String name, String url) {
        System.out.println("query code from " + url + "...");
        try {
            Thread.sleep((long) (Math.random() * 100));
        } catch (InterruptedException e) {
        }
        return "601857";
    }

    static Double fetchPrice(String code, String url) {
        System.out.println("query price from " + url + "...");
        try {
            Thread.sleep((long) (Math.random() * 100));
        } catch (InterruptedException e) {
        }
        return 5 + Math.random() * 20;
    }

}

結果:

query code from https://finance.sina.com.cn/code/...
query code from https://money.163.com/code/...
query price from https://finance.sina.com.cn/price/...
query price from https://money.163.com/price/...
price: 7.50078323021774

分析

需要注意一點,雖然是異步的從一個地方取值,但是其他任務依然會執行完成,而並非不再執行了

組合方法

組合兩個任務,同時處理兩個結果

public <U> CompletableFuture<Void> thenAcceptBoth(CompletionStage<? extends U> other,
                                                  BiConsumer<? super T,? super U> action)
public <U> CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,
                                                       BiConsumer<? super T,? super U> action)
public <U> CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,
                                                       BiConsumer<? super T,? super U> action,
                                                       Executor executor)
                                                       

舉例

public class CompletableFutureTest {

    public static void main(String[] args) throws Exception {
        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> "Hello");

        CompletableFuture<Void> future = completableFuture.thenAcceptBoth(CompletableFuture.supplyAsync(() -> 100),
                (s,i)-> System.out.println("s: "+ s + ", i: " + i));
        Thread.currentThread().join();
    }
}

結果

s: Hello, i: 100

分析

  • 可以看出是兩個任務組合,然後同時將兩個結果一起處理

組合兩個任務,任務完成後做的操作

public CompletableFuture<Void> runAfterBoth(CompletionStage<?> other,
                                            Runnable action)                                            
public CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other,
                                                 Runnable action)
public CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other,
                                                 Runnable action)

分析

  • 相當於組合兩個任務,執行thenRun

當兩個任務任意一個執行完成後,執行一個操作

public CompletableFuture<Void> runAfterEither(CompletionStage<?> other,
                                              Runnable action)
public CompletableFuture<Void> runAfterEitherAsync(CompletionStage<?> other,
                                                   Runnable action)
public CompletableFuture<Void> runAfterEitherAsync(CompletionStage<?> other,
                                                   Runnable action)

舉例

public class CompletableFutureTest {

    public static void main(String[] args) throws Exception {
        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println("Hello");
            return "a";
        });

        CompletableFuture<Void> future = completableFuture.runAfterEitherAsync(CompletableFuture.supplyAsync(() -> {
                    System.out.println(100);
                    return 100;
                }),
                () -> System.out.println("done."));
        Thread.currentThread().join();
    }

}

結果

Hello
100
done.

分析

  • 相當於一次同步任務

組合兩個任務,處理後,返回一個結果

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

舉例

public class CompletableFutureTest {

    public static void main(String[] args) throws Exception {
        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> "a");

        CompletableFuture<Boolean> future = completableFuture.thenCombine(CompletableFuture.supplyAsync(() -> 100),
                (s,i)->{
                    System.out.println("s: " + s +" , i : " + i);
                    return true;
                });
        System.out.println(future.get());
        Thread.currentThread().join();
    }
}

結果

s: a , i : 100
true

合併兩個任務,第一個任務的輸出是第二個任務的輸入

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)

分析

  • 相當於一次級聯操作

中轉方法

有返回值

當執行完成時執行的操作

public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)

舉例

public class CompletableFutureTest {
    public static void main(String[] args) throws Exception {
        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> "Hello");
        CompletableFuture<String> future = completableFuture.whenComplete((v, t) -> System.out.println(v + " World !"));
        System.out.println(future.get());
        Thread.currentThread().join();
    }
}

結果

Hello World !
Hello

分析

  • 當執行完成時執行的回調方法
  • 該方法會接收執行的結果以及異常
  • 回調完成會,會把任務執行的結果傳遞回去
  • whenCompleteAsync是異步的;whenComplete是同步的,會卡住主線程
  • 需要傳遞一個BiConsumer接口,如下所示:
public interface BiConsumer<T, U> {
    void accept(T t, U u);
  • 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 class CompletableFutureTest {

    public static void main(String[] args) throws Exception {
        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> "Hello");

        CompletableFuture<Integer> future = completableFuture.thenApplyAsync(t -> {
            String s =  t + " World !";
            System.out.println(s);
            return s.length();
        });
        System.out.println(future.get());
        Thread.currentThread().join();
    }

}
public interface Function<T, R> {
    R apply(T t);

結果

Hello World !
13

分析

  • 是一個級聯操作,即拿着上個任務的結果,做下個任務,同時返回一個新的結果

處理結果的操作

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

舉例

public class CompletableFutureTest {

    public static void main(String[] args) throws Exception {
        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> "Hello");

        CompletableFuture<Integer> future = completableFuture.handleAsync((s,t) -> {
            String aaa =  t + " World !";
            System.out.println(aaa);
            return aaa.length();
        });
        System.out.println(future.get());
        Thread.currentThread().join();
    }

}

結果

Hello World !
13

分析

  • 相比於whenComplete返回值可以自己處理,相當於一次級聯
  • 相比於thenApply可以處理異常

無返回值

處理結果

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

舉例

public class CompletableFutureTest {

    public static void main(String[] args) throws Exception {
        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> "Hello");

        CompletableFuture<Void> future = completableFuture.thenAccept(t -> {
            String aaa =  t + " World !";
            System.out.println(aaa);
        });
        System.out.println(future.get());
        Thread.currentThread().join();
    }

}

結果

Hello World !
null

分析

  • 相當於一次級聯,但是沒有返回值

執行完全部任務

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

分析

  • 相較thenAccept,不處理任務的執行結果

終結方法

處理異常

public CompletableFuture<T> exceptionally(Function<Throwable,? extends T> fn)
public class CompletableFutureTest {

    public static void main(String[] args) throws Exception {
        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
            int a  = 1/0;
            return "World ";
        });
        completableFuture.exceptionally(Throwable::getMessage).thenAccept(t->{
            System.out.println(t);
        });

        Thread.currentThread().join();
    }
}

立馬獲取結果

public T getNow(T valueIfAbsent)

舉例

public class CompletableFutureTest {

    public static void main(String[] args) throws Exception {
        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> "World ");

        Thread.sleep(1000);
        String now = completableFuture.getNow("Hello");
        System.out.println(now);
        System.out.println(completableFuture.get());
        Thread.currentThread().join();
    }

}

結果

World 
World 

分析

  • 如果結果完成返回結果,如果未完成,返回傳入進去的值

判斷結果是否完成,如果未完成則賦予結果

public boolean complete(T value)

判斷結果是否完成,如果未完成返回異常

public boolean completeExceptionally(Throwable ex)

後續獲取結果會產生異常

public void obtrudeException(Throwable ex)

總結

  • thenAccept()處理正常結果;
  • exceptionally()處理異常結果;
  • thenApplyAsync()用於串行化另一個CompletableFuture
  • anyOf()和allOf()用於並行化多個CompletableFuture
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章