Java8 CompletableFuture

GGuoLiang 發表於 2020-09-08

CompletableFuture

Future介面

引入Future介面,設計初衷是對將來某個時刻會發生的結果進行建模。

CompletableFuture

實現Future介面,提供了更為強大的非同步操作。

淺嘗

編寫一個aplle手機的價格查詢器。建立apple類的價格查詢方法。

public Double findPrice(String name){
      try {
          TimeUnit.SECONDS.sleep(1);
      } catch (InterruptedException e) {
          e.printStackTrace();
      }
      Random random = new Random();
      return random.nextDouble()+name.charAt(0)+name.charAt(name.length()-1);
  }
  1. Funture介面方式

    ExecutorService executorService = Executors.newFixedThreadPool(10);
    Apple apple = new Apple();
    Future<Double> iphone7 = executorService.submit(() -> apple.findPrice("iphone7"));
  2. CompletableFuture介面方式

    ExecutorService executorService = Executors.newFixedThreadPool(10);
    Apple apple = new Apple();
    CompletableFuture<Double> completableFuture = new CompletableFuture<>();
    executorService.submit(()->{
        Double ipone7_pro = apple.findPrice("ipone7 pro");
        completableFuture.complete(ipone7_pro);
    });

異常處理

當價格計算過程中產生了錯誤會怎樣呢?結果是:用於提示錯誤的異常會被限制在試圖計算商品價格的當前執行緒的範圍內,最終會殺死該執行緒,而這會導致等待get方法返回結果的客戶端永久地被阻塞。

面對這個問題解決方案:

  1. 使用get的過載版本,新增超時時間

  2. 將異常丟擲。

    ExecutorService executorService = Executors.newFixedThreadPool(10);
    Apple apple = new Apple();
    CompletableFuture<Double> completableFuture = new CompletableFuture<>();
    executorService.submit(()->{
        Double ipone7_pro = apple.findPrice("ipone7 pro");
        try {
            completableFuture.complete(ipone7_pro);
        }catch (Exception ex){
            completableFuture.completeExceptionally(ex);
        }
    });

推薦的做法:使用第二種方式,可以得知是什麼原因導致。

supplyAsync

CompletableFuture類自身提供了大量精巧的工廠方法,使用這些方法能更容易地完成整個流程,還不用擔心實現的細節。比如常用的supplyAsync方法。

Apple apple = new Apple();
CompletableFuture<Double> completableFuture = CompletableFuture.supplyAsync(() -> apple.findPrice("iphone8"));

supplyAsync方法接受一個生產者(Supplier)作為引數,返回一個CompletableFuture物件,該物件完成非同步執行後會讀取呼叫生產者方法的返回值。生產者方法會交由ForkJoinPool池中的某個執行執行緒(Executor)執行,但是也可以使用supplyAsync方法的過載版本,傳遞第二個引數指定不同的執行執行緒執行生產者方法。

Apple apple = new Apple();
CompletableFuture<Double> completableFuture = CompletableFuture.supplyAsync(() -> apple.findPrice("iphone8"),Executors.newFixedThreadPool(10));

集合操作

出現多個手機名稱需要查詢價格時:

 List<String> list = Arrays.asList("ipone4""ipone5""ipone5s""ipone6""ipone6s""ipone7""ipone7s""ipone8""iponex");
  1. Stream

    List<Double> collect = list.stream().map(string -> apple.findPrice(string)).collect(Collectors.toList());
  2. parallelStream

     List<Double> collect1 = list.parallelStream().map(string -> apple.findPrice(string)).collect(Collectors.toList());
  3. CompletableFuture

    這裡使用了兩個不同的Stream流水線:如果在單一流水線中處理流,那麼發向不同商家的請求只能以同步、順序執行的方式才會成功。

    List<CompletableFuture<Double>> collect2 = list.stream().map(str -> CompletableFuture.supplyAsync(() -> apple.findPrice(str))).collect(Collectors.toList());
    List<Double> collect3 = collect2.stream().map(CompletableFuture::join).collect(Collectors.toList());

效能比較:

image-1

看到parallelStream和CompletableFuture不相伯仲,原因是:它們內部採用的是同樣的通用執行緒池,預設都使用固定數目的執行緒,具體執行緒數取決於Runtime.getRuntime(). availableProcessors()的返回值。然而,CompletableFuture具有一定的優勢,因為它允許你對執行器(Executor)進行配置,尤其是執行緒池的大小,讓它以更適合應用需求的方式進行配置,滿足程式的要求,而這是並行流API無法提供的。

定製執行器

當集合數量增到20時;

// parallelStream
long l2 = System.currentTimeMillis();
List<Double> collect1 = list.parallelStream().map(string -> apple.findPrice(string)).collect(Collectors.toList());
System.out.println("ParallelStream:"+(System.currentTimeMillis() - l2));

//CompletableFuture
long l3 = System.currentTimeMillis();
List<CompletableFuture<Double>> collect2 = list.stream().map(str -> CompletableFuture.supplyAsync(() -> apple.findPrice(str),Executors.newFixedThreadPool(20))).collect(Collectors.toList());
List<Double> collect3 = collect2.stream().map(CompletableFuture::join).collect(Collectors.toList());
System.out.println("CompletableFuture:"+(System.currentTimeMillis() - l3));

執行結果:

image-2

並行選擇

可以遵循以下建議:

  1. 如果你進行的是計算密集型的操作,並且沒有I/O,那麼推薦使用Stream介面,因為實現簡單,同時效率也可能是最高的(如果所有的執行緒都是計算密集型的,那就沒有必要建立比處理器核數更多的執行緒)。
  2. 如果你並行的工作單元還涉及等待I/O的操作(包括網路連線等待),那麼使用CompletableFuture靈活性更好,你可以像前文討論的那樣,依據等待/計算,或者W/C的比率設定需要使用的執行緒數。這種情況不使用並行流的另一個原因是,處理流的流水線中如果發生I/O等待,流的延遲特性會讓我們很難判斷到底什麼時候觸發了等待。

常用方法

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有返回值。

thenApply

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

//Function<? super T,? extends 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)

執行:

 CompletableFuture<String> stringCompletableFuture
   = CompletableFuture.supplyAsync(() -> "GGuo").thenApply(string -> string + " Liang");
 System.out.println(stringCompletableFuture.join());

//結果
//GGuo Liang

thenAccept

消耗結果

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);

執行:

CompletableFuture.supplyAsync(()->"GGuoLiang").thenAccept(System.out::println);

// 結果
//GGuoLiang

thenRun

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

執行:

CompletableFuture.supplyAsync(()->{
    System.out.println("當前執行緒:"+Thread.currentThread().getName());
    return "GGuo";
}).thenRun(()-> {
    System.out.println("當前執行緒:"+Thread.currentThread().getName());
    System.out.println("Liang");
});

// 結果
當前執行緒:ForkJoinPool.commonPool-worker-9
當前執行緒:main
Liang

thenCompose

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

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) ;

執行:

  String join = CompletableFuture.supplyAsync(() -> {
      System.out.println("當前執行緒:"+Thread.currentThread().getName()+" 執行時間 "+System.currentTimeMillis());
      try {
          TimeUnit.SECONDS.sleep(1);
      } catch (InterruptedException e) {
          e.printStackTrace();
      }
      return "GGuo";
  }).thenCompose(string -> CompletableFuture.supplyAsync(() -> {
      try {
          TimeUnit.SECONDS.sleep(1);
      } catch (InterruptedException e) {
          e.printStackTrace();
      }
      System.out.println("當前執行緒:"+Thread.currentThread().getName()+" 執行時間 "+System.currentTimeMillis());
      return string + "Liang";
  })).join();
  System.out.println(join);

// 結果
當前執行緒:ForkJoinPool.commonPool-worker-9 執行時間 1599552921454
當前執行緒:ForkJoinPool.commonPool-worker-9 執行時間 1599552923457
GGuoLiang

thenCombine

合併倆個CompletableFuture操作

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);

執行:

String join = CompletableFuture.supplyAsync(() -> "GGuoLiang")
        .thenCombine(CompletableFuture.supplyAsync(() -> "Liang"),
                (a, b) -> a + b).join();
System.out.println(join);

//結果
GGuoliangLiang

thenAcceptBoth

thenAccept升級版,消耗倆個CompletableFuture操作。

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

執行:

CompletableFuture.supplyAsync(()->"GGuoLiang")
        .thenAcceptBoth(CompletableFuture.supplyAsync(()->"thenAcceptBoth"),
                (a,b)-> System.out.println(a+" 測試 "+b));

runAfterBoth

thenRun升級版,倆個CompletableFuture操作執行完畢。

public CompletionStage<Void> runAfterBoth(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action,Executor executor);

執行:

CompletableFuture.supplyAsync(()->"GGuoLiang")
        .runAfterBoth(CompletableFuture.supplyAsync(()->"runAfterBoth"),
                ()-> System.out.println("執行完畢"));

// 結果
執行完畢

runAfterEither

只要執行完畢一個操作,就往下執行。

public CompletionStage<Void> runAfterEither(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action,Executor executor);

執行:

CompletableFuture.supplyAsync(() -> {
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return "GGuoLiang";
}).runAfterEither(CompletableFuture.supplyAsync(() -> "runAfterBoth"), 
        ()-> System.out.println("執行完畢"));

//結果
執行完畢

applyToEither

獲取快的結果。

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);

執行:

String join = CompletableFuture.supplyAsync(() -> {
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return "GGuoLiang";
}).applyToEither(CompletableFuture.supplyAsync(() -> "runAfterBoth"), a -> a).join();
System.out.println(join);

//結果
runAfterBoth

acceptEither

消耗快的結果。

public CompletionStage<Void> acceptEither(CompletionStage<? extends T> other,Consumer<? super T> action);
public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,Consumer<? super T> action);
public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,Consumer<? super T> action,Executor executor);

執行:

CompletableFuture.supplyAsync(() -> {
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return "GGuoLiang";
}).acceptEither(CompletableFuture.supplyAsync(() -> "runAfterBoth"), System.out::println);
}

//結果
runAfterBoth

exceptionally

異常處理。

public CompletionStage<T> exceptionally(Function<Throwable, ? extends T> fn);

執行:

Object str = CompletableFuture.supplyAsync(() -> {
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    throw new RuntimeException("exceptionally 異常測試");
}).exceptionally(Throwable::getMessage).join();
System.out.println(str.toString());

// 結果
java.lang.RuntimeException: exceptionally 異常測試

whenComplete

消費執行的正常的結果或異常的結果。

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

執行:

String  whenComplete = CompletableFuture.supplyAsync(() -> {
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    if (true) {
        throw new RuntimeException("whenComplete 異常測試");
    }
    return "GGuoLiang";
}).whenComplete((s, t) -> {
   if(s == null){
       System.out.println(t.getMessage());
   }else{
       System.out.println(s);
   }
}).join();
System.out.println(whenComplete);

//執行結果
java.lang.RuntimeException: whenComplete 異常測試

handle

返回執行的正常的結果或異常的結果。

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);

執行:

String  handle = CompletableFuture.supplyAsync(() -> {
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    if (true) {
        throw new RuntimeException("whenComplete 異常測試");
    }
    return "GGuoLiang";
}).handle((s, t) -> {
    if(s == null){
       return t.getMessage();
    }else{
       return s;
    }
}).join();
System.out.println(handle);

//結果
java.lang.RuntimeException: whenComplete 異常測試