14-Future&CompletableFuture

Callable&Future&FutureTask

直接繼承Thread或者實現Runnable接口都可以創建線程,但是這兩種方法都有一個問題就是:沒有返回值,也就是不能獲取執行完的結果。因此java1.5就提供了Callable接口來實現這一場景,而Future和FutureTask就可以和Callable接口配合起來使用。

Callable和Runnable的區別

思考:爲什麼需要 Callable?

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

Runnable 的缺陷:

  • 不能返回一個返回值
  • 不能拋出 checked Exception

Callable的call方法可以有返回值,可以聲明拋出異常。和 Callable 配合的有一個 Future 類,通過 Future 可以瞭解任務執行情況,或者取消任務的執行,還可獲取任務執行的結果,這些功能都是 Runnable 做不到的,Callable 的功能要比 Runnable 強大。

new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("通過Runnable方式執行任務");
    }
}).start();

FutureTask task = new FutureTask(new Callable() {
    @Override
    public Object call() throws Exception {
        System.out.println("通過Callable方式執行任務");
        Thread.sleep(3000);
        return "返回任務結果";
    }
});
new Thread(task).start();

Future 的主要功能

Future就是對於具體的Runnable或者Callable任務的執行結果進行取消、查詢是否完成、獲取結果。****必要時可以通過get方法獲取執行結果,該方法會阻塞直到任務返回結果。

  • boolean cancel (boolean mayInterruptIfRunning) 取消任務的執行。參數指定是否立即中斷任務執行,或者等等任務結束
  • boolean isCancelled () 任務是否已經取消,任務正常完成前將其取消,則返回 true
  • boolean isDone () 任務是否已經完成。需要注意的是如果任務正常終止、異常或取消,都將返回true
  • V get () throws InterruptedException, ExecutionException 等待任務執行結束,然後獲得V類型的結果。InterruptedException 線程被中斷異常, ExecutionException任務執行異常,如果任務被取消,還會拋出CancellationException
  • V get (long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException 同上面的get功能一樣,多了設置超時時間。參數timeout指定超時時間,uint指定時間的單位,在枚舉類TimeUnit中有相關的定義。如果計算超時,將拋出TimeoutException

image

利用 FutureTask 創建 Future

Future實際採用FutureTask實現,該對象相當於是消費者和生產者的橋樑,消費者通過 FutureTask 存儲任務的處理結果,更新任務的狀態:未開始、正在處理、已完成等。而生產者拿到的 FutureTask 被轉型爲 Future 接口,可以阻塞式獲取任務的處理結果,非阻塞式獲取任務處理狀態。

FutureTask既可以被當做Runnable來執行,也可以被當做Future來獲取Callable的返回結果。

image

如何使用

把 Callable 實例當作 FutureTask 構造函數的參數,生成 FutureTask 的對象,然後把這個對象當作一個 Runnable 對象,放到線程池中或另起線程去執行,最後還可以通過 FutureTask 獲取任務執行的結果。

public class FutureTaskDemo {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Task task = new Task();
        //構建futureTask
        FutureTask<Integer> futureTask = new FutureTask<>(task);
        //作爲Runnable入參
        new Thread(futureTask).start();

        System.out.println("task運行結果:"+futureTask.get());
    }

    static class Task implements Callable<Integer> {

        @Override
        public Integer call() throws Exception {
            System.out.println("子線程正在計算");
            int sum = 0;
            for (int i = 0; i < 100; i++) {
                sum += i;
            }
            return sum;
        }
    }
}

使用案例:促銷活動中商品信息查詢

在維護促銷活動時需要查詢商品信息(包括商品基本信息、商品價格、商品庫存、商品圖片、商品銷售狀態等)。這些信息分佈在不同的業務中心,由不同的系統提供服務。如果採用同步方式,假設一個接口需要50ms,那麼一個商品查詢下來就需要200ms-300ms,這對於我們來說是不滿意的。如果使用Future改造則需要的就是最長耗時服務的接口,也就是50ms左右。

image

public class FutureTaskDemo2 {

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

        FutureTask<String> ft1 = new FutureTask<>(new T1Task());
        FutureTask<String> ft2 = new FutureTask<>(new T2Task());
        FutureTask<String> ft3 = new FutureTask<>(new T3Task());
        FutureTask<String> ft4 = new FutureTask<>(new T4Task());
        FutureTask<String> ft5 = new FutureTask<>(new T5Task());

        //構建線程池
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        executorService.submit(ft1);
        executorService.submit(ft2);
        executorService.submit(ft3);
        executorService.submit(ft4);
        executorService.submit(ft5);
        //獲取執行結果
        System.out.println(ft1.get());
        System.out.println(ft2.get());
        System.out.println(ft3.get());
        System.out.println(ft4.get());
        System.out.println(ft5.get());

        executorService.shutdown();

    }

    static class T1Task implements Callable<String> {
        @Override
        public String call() throws Exception {
            System.out.println("T1:查詢商品基本信息...");
            TimeUnit.MILLISECONDS.sleep(50);
            return "商品基本信息查詢成功";
        }
    }

    static class T2Task implements Callable<String> {
        @Override
        public String call() throws Exception {
            System.out.println("T2:查詢商品價格...");
            TimeUnit.MILLISECONDS.sleep(50);
            return "商品價格查詢成功";
        }
    }

    static class T3Task implements Callable<String> {
        @Override
        public String call() throws Exception {
            System.out.println("T3:查詢商品庫存...");
            TimeUnit.MILLISECONDS.sleep(50);
            return "商品庫存查詢成功";
        }
    }

    static class T4Task implements Callable<String> {
        @Override
        public String call() throws Exception {
            System.out.println("T4:查詢商品圖片...");
            TimeUnit.MILLISECONDS.sleep(50);
            return "商品圖片查詢成功";
        }
    }

    static class T5Task implements Callable<String> {
        @Override
        public String call() throws Exception {
            System.out.println("T5:查詢商品銷售狀態...");
            TimeUnit.MILLISECONDS.sleep(50);
            return "商品銷售狀態查詢成功";
        }
    }
}

Future 注意事項

  • 當 for 循環批量獲取 Future 的結果時容易 block,get 方法調用時應使用 timeout 限制
  • Future 的生命週期不能後退。一旦完成了任務,它就永久停在了“已完成”的狀態,不能從頭再來

思考: 使用Callable 和Future 產生新的線程了嗎?

Future的侷限性

從本質上說,Future表示一個異步計算的結果。它提供了isDone()來檢測計算是否已經完成,並且在計算結束後,可以通過get()方法來獲取計算結果。在異步計算中,Future確實是個非常優秀的接口。但是,它的本身也確實存在着許多限制:

  • 併發執行多任務:Future只提供了get()方法來獲取結果,並且是阻塞的。所以,除了等待你別無他法;
  • 無法對多個任務進行鏈式調用:如果你希望在計算任務完成後執行特定動作,比如發郵件,但Future卻沒有提供這樣的能力;
  • 無法組合多個任務:如果你運行了10個任務,並期望在它們全部執行結束後執行特定動作,那麼在Future中這是無能爲力的;
  • 沒有異常處理:Future接口中沒有關於異常處理的方法;

CompletionService

Callable+Future 可以實現多個task並行執行,但是如果遇到前面的task執行較慢時需要阻塞等待前面的task執行完後面task才能取得結果。而CompletionService的主要功能就是一邊生成任務,一邊獲取任務的返回值。讓兩件事分開執行,任務之間不會互相阻塞,可以實現先執行完的先取結果,不再依賴任務順序了。

image

CompletionService原理

內部通過阻塞隊列+FutureTask,實現了任務先完成可優先獲取到,即結果按照完成先後順序排序,內部有一個先進先出的阻塞隊列,用於保存已經執行完成的Future,通過調用它的take方法或poll方法可以獲取到一個已經執行完成的Future,進而通過調用Future接口實現類的get方法獲取最終的結果

使用案例

詢價應用:向不同電商平臺詢價,並保存價格

  • 採用“ThreadPoolExecutor+Future”的方案:異步執行詢價然後再保存
//    創建線程池 
ExecutorService    executor = Executors.newFixedThreadPool(3); 
//    異步向電商S1詢價 
Future<Integer>    f1 = executor.submit(()->getPriceByS1()); 
//    異步向電商S2詢價 
Future<Integer>    f2=    executor.submit(()->getPriceByS2());             
//    獲取電商S1報價並異步保存 
executor.execute(()->save(f1.get()));        
//    獲取電商S2報價並異步保存 
executor.execute(()->save(f2.get())        

如果獲取電商S1報價的耗時很長,那麼即便獲取電商S2報價的耗時很短,也無法讓保存S2報價的操作先執行,因爲這個主線程都阻塞 在了f1.get()操作上。

  • 使用CompletionService實現先獲取的報價先保存到數據庫
//創建線程池
ExecutorService executor = Executors.newFixedThreadPool(10);
//創建CompletionService
CompletionService<Integer> cs = new ExecutorCompletionService<>(executor);
//異步向電商S1詢價
cs.submit(() -> getPriceByS1());
//異步向電商S2詢價
cs.submit(() -> getPriceByS2());
//異步向電商S3詢價
cs.submit(() -> getPriceByS3());
//將詢價結果異步保存到數據庫
for (int i = 0; i < 3; i++) {
    Integer r = cs.take().get();
    executor.execute(() -> save(r));
}

實現類似 Dubbo 的 Forking Cluster場景

Dubbo 中有一種叫做 Forking 的集羣模式,這種集羣模式下,支持並行地調用多個服務實例,只要有一個成功就返回結果。

geocoder(addr) {
  //並行執行以下3個查詢服務, 
  r1=geocoderByS1(addr);
  r2=geocoderByS2(addr);
  r3=geocoderByS3(addr);
  //只要r1,r2,r3有一個返回
  //則返回
  return r1|r2|r3;
}
// 創建線程池
ExecutorService executor = Executors.newFixedThreadPool(3);
// 創建CompletionService
CompletionService<Integer> cs = new ExecutorCompletionService<>(executor);
// 用於保存Future對象
List<Future<Integer>> futures = new ArrayList<>(3);
//提交異步任務,並保存future到futures 
futures.add(cs.submit(()->geocoderByS1()));
futures.add(cs.submit(()->geocoderByS2()));
futures.add(cs.submit(()->geocoderByS3()));
// 獲取最快返回的任務執行結果
Integer r = 0;
try {
  // 只要有一個成功返回,則break
  for (int i = 0; i < 3; ++i) {
    r = cs.take().get();
    //簡單地通過判空來檢查是否成功返回
    if (r != null) {
      break;
    }
  }
} finally {
  //取消所有任務
  for(Future<Integer> f : futures)
    f.cancel(true);
}
// 返回結果

應用場景總結

  • 當需要批量提交異步任務的時候建議你使用CompletionService。CompletionService將線程池Executor和阻塞隊列BlockingQueue的功能融合在了一起,能夠讓批量異步任務的管理更簡單。
  • CompletionService能夠讓異步任務的執行結果有序化。先執行完的先進入阻塞隊列,利用這個特性,你可以輕鬆實現後續處理的有序性,避免無謂的等待,同時還可以快速實現諸如Forking Cluster這樣的需求。
  • 線程池隔離。CompletionService支持自己創建線程池,這種隔離性能避免幾個特別耗時的任務拖垮整個應用的風險。

CompletableFuture使用詳解

簡單的任務,用Future獲取結果還好,但我們並行提交的多個異步任務,往往並不是獨立的,很多時候業務邏輯處理存在串行[依賴]、並行、聚合的關係。如果要我們手動用 Fueture 實現,是非常麻煩的。

CompletableFuture是Future接口的擴展和增強。CompletableFuture實現了Future接口,並在此基礎上進行了豐富地擴展,完美地彌補了Future上述的種種問題。更爲重要的是,CompletableFuture實現了對任務的編排能力。藉助這項能力,我們可以輕鬆地組織不同任務的運行順序、規則以及方式。從某種程度上說,這項能力是它的核心能力。而在以往,雖然通過CountDownLatch等工具類也可以實現任務的編排,但需要複雜的邏輯處理,不僅耗費精力且難以維護。

jdk8 API文檔:https://docs.oracle.com/javase/8/docs/api/

image

CompletionStage接口: 執行某一個階段,可向下執行後續階段。異步執行,默認線程池是ForkJoinPool.commonPool()

應用場景

描述依賴關係:

  1. thenApply() 把前面異步任務的結果,交給後面的Function
  2. thenCompose()用來連接兩個有依賴關係的任務,結果由第二個任務返回

描述and聚合關係:

  1. thenCombine:任務合併,有返回值
  2. thenAccepetBoth:兩個任務執行完成後,將結果交給thenAccepetBoth消耗,無返回值。
  3. runAfterBoth:兩個任務都執行完成後,執行下一步操作(Runnable)。

描述or聚合關係:

  1. applyToEither:兩個任務誰執行的快,就使用那一個結果,有返回值。
  2. acceptEither: 兩個任務誰執行的快,就消耗那一個結果,無返回值。
  3. runAfterEither: 任意一個任務執行完成,進行下一步操作(Runnable)。

並行執行:

CompletableFuture類自己也提供了anyOf()和allOf()用於支持多個CompletableFuture並行執行

創建異步操作

CompletableFuture 提供了四個靜態方法來創建一個異步操作:

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 方法以Runnable函數式接口類型爲參數,沒有返回結果,supplyAsync 方法Supplier函數式接口類型爲參數,返回結果類型爲U;Supplier 接口的 get() 方法是有返回值的(會阻塞
  • 沒有指定Executor的方法會使用ForkJoinPool.commonPool() 作爲它的線程池執行異步代碼。如果指定線程池,則使用指定的線程池運行。
  • 默認情況下 CompletableFuture 會使用公共的 ForkJoinPool 線程池,這個線程池默認創建的線程數是 CPU 的核數(也可以通過 JVM option:-Djava.util.concurrent.ForkJoinPool.common.parallelism 來設置 ForkJoinPool 線程池的線程數)。如果所有 CompletableFuture 共享一個線程池,那麼一旦有任務執行一些很慢的 I/O 操作,就會導致線程池中所有線程都阻塞在 I/O 操作上,從而造成線程飢餓,進而影響整個系統的性能。所以,強烈建議你要根據不同的業務類型創建不同的線程池,以避免互相干擾

runAsync&supplyAsync

Runnable runnable = () -> System.out.println("執行無返回結果的異步任務");
CompletableFuture.runAsync(runnable);

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    System.out.println("執行有返回值的異步任務");
    try {
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return "Hello World";
});
String result = future.get();

執行無返回結果的異步任務

執行有返回值的異步任務

獲取結果

join&get

join()和get()方法都是用來獲取CompletableFuture異步之後的返回值。join()方法拋出的是uncheck異常(即未經檢查的異常),不會強制開發者拋出。get()方法拋出的是經過檢查的異常,ExecutionException, InterruptedException 需要用戶手動處理(拋出或者 try catch)

結果處理

當CompletableFuture的計算結果完成,或者拋出異常的時候,我們可以執行特定的 Action。主要是下面的方法:

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)
  • Action的類型是BiConsumer,它可以處理正常的計算結果,或者異常情況。
  • 方法不以Async結尾,意味着Action使用相同的線程執行,而Async可能會使用其它的線程去執行(如果使用相同的線程池,也可能會被同一個線程選中執行)。
  • 這幾個方法都會返回CompletableFuture,當Action執行完畢後它的結果返回原始的CompletableFuture的計算結果或者返回異常

whenComplete&exceptionally

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
    }
    if (new Random().nextInt(10) % 2 == 0) {
        int i = 12 / 0;
    }
    System.out.println("執行結束!");
    return "test";
});

future.whenComplete(new BiConsumer<String, Throwable>() {
    @Override
    public void accept(String t, Throwable action) {
        System.out.println(t+" 執行完成!");
    }
});

future.exceptionally(new Function<Throwable, String>() {
    @Override
    public String apply(Throwable t) {
        System.out.println("執行失敗:" + t.getMessage());
        return "異常xxxx";
    }
}

結果

執行結束!
test 執行完成!
或者
執行失敗:java.lang.ArithmeticException: / by zero
null 執行完成!

結果轉換

所謂結果轉換,就是將上一段任務的執行結果作爲下一階段任務的入參參與重新計算,產生新的結果。

thenApply

thenApply 接收一個函數作爲參數,使用該函數處理上一個CompletableFuture 調用的結果,並返回一個具有處理結果的Future對象。

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


CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    int result = 100;
    System.out.println("一階段:" + result);
    return result;
}).thenApply(number -> {
    int result = number * 3;
    System.out.println("二階段:" + result);
    return result;
});

結果

一階段:100
二階段:300
最終結果:300

thenCompose

thenCompose 的參數爲一個返回 CompletableFuture 實例的函數,該函數的參數是先前計算步驟的結果。

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

CompletableFuture<Integer> future = CompletableFuture
        .supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                int number = new Random().nextInt(30);
                System.out.println("第一階段:" + number);
                return number;
            }
        })
        .thenCompose(new Function<Integer, CompletionStage<Integer>>() {
            @Override
            public CompletionStage<Integer> apply(Integer param) {
                return CompletableFuture.supplyAsync(new Supplier<Integer>() {
                    @Override
                    public Integer get() {
                        int number = param * 2;
                        System.out.println("第二階段:" + number);
                        return number;
                    }
                });
            }
        });
        

}

結果

第一階段:10
第二階段:20
最終結果: 20

thenApply 和 thenCompose的區別

  • thenApply 轉換的是泛型中的類型,返回的是同一個CompletableFuture;
  • thenCompose 將內部的 CompletableFuture 調用展開來並使用上一個CompletableFutre 調用的結果在下一步的 CompletableFuture 調用中進行運算,是生成一個新的CompletableFuture。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello");

CompletableFuture<String> result1 = future.thenApply(param -> param + " World");
CompletableFuture<String> result2 = future
        .thenCompose(param -> CompletableFuture.supplyAsync(() -> param + " World"));

System.out.println(result1.get());

結果

Hello World
Hello World

結果消費

與結果處理和結果轉換系列函數返回一個新的 CompletableFuture 不同,結果消費系列函數只對結果執行Action,而不返回新的計算值。

根據對結果的處理方式,結果消費函數又分爲:

  • thenAccept系列:對單個結果進行消費
  • thenAcceptBoth系列:對兩個結果進行消費
  • thenRun系列:不關心結果,只對結果執行Action

thenAccept

通過觀察該系列函數的參數類型可知,它們是函數式接口Consumer,這個接口只有輸入,沒有返回值。

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

CompletableFuture<Void> future = CompletableFuture
        .supplyAsync(() -> {
            int number = new Random().nextInt(10);
            System.out.println("第一階段:" + number);
            return number;
        }).thenAccept(number ->
                System.out.println("第二階段:" + number * 5));
}

結果

第一階段:8
第二階段:40
最終結果:null

thenAcceptBoth

thenAcceptBoth 函數的作用是,當兩個 CompletionStage 都正常完成計算的時候,就會執行提供的action消費兩個異步的結果。

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

CompletableFuture<Integer> futrue1 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
    @Override
    public Integer get() {
        int number = new Random().nextInt(3) + 1;
        try {
            TimeUnit.SECONDS.sleep(number);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("第一階段:" + number);
        return number;
    }
});

CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
    @Override
    public Integer get() {
        int number = new Random().nextInt(3) + 1;
        try {
            TimeUnit.SECONDS.sleep(number);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("第二階段:" + number);
        return number;
    }
});

futrue1.thenAcceptBoth(future2, new BiConsumer<Integer, Integer>() {
    @Override
    public void accept(Integer x, Integer y) {
        System.out.println("最終結果:" + (x + y));
    }
}

結果

第二階段:1
第一階段:2
最終結果:3

thenRun

thenRun 也是對線程任務結果的一種消費函數,與thenAccept不同的是,thenRun 會在上一階段 CompletableFuture 計算完成的時候執行一個Runnable,Runnable並不使用該 CompletableFuture 計算的結果。

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

CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
    int number = new Random().nextInt(10);
    System.out.println("第一階段:" + number);
    return number;
}).thenRun(() ->
        System.out.println("thenRun 執行"));
)

結果

第一階段:2
thenRun 執行
最終結果:null

結果組合

thenCombine

thenCombine 方法,合併兩個線程任務的結果,並進一步處理。

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

CompletableFuture<Integer> future1 = CompletableFuture
        .supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                int number = new Random().nextInt(10);
                System.out.println("第一階段:" + number);
                return number;
            }
        });
CompletableFuture<Integer> future2 = CompletableFuture
        .supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                int number = new Random().nextInt(10);
                System.out.println("第二階段:" + number);
                return number;
            }
        });
CompletableFuture<Integer> result = future1
        .thenCombine(future2, new BiFunction<Integer, Integer, Integer>() {
            @Override
            public Integer apply(Integer x, Integer y) {
                return x + y;
            }
        });
}

結果

第一階段:9
第二階段:5
最終結果:14

任務交互

所謂線程交互,是指將兩個線程任務獲取結果的速度相比較,按一定的規則進行下一步處理。

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

CompletableFuture<Integer> future1 = CompletableFuture
        .supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                int number = new Random().nextInt(10);
                System.out.println("第一階段start:" + number);
                try {
                    TimeUnit.SECONDS.sleep(number);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("第一階段end:" + number);
                return number;
            }
        });
CompletableFuture<Integer> future2 = CompletableFuture
        .supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                int number = new Random().nextInt(10);
                System.out.println("第二階段start:" + number);
                try {
                    TimeUnit.SECONDS.sleep(number);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("第二階段end:" + number);
                return number;
            }
        });

future1.applyToEither(future2, new Function<Integer, Integer>() {
    @Override
    public Integer apply(Integer number) {
        System.out.println("最快結果:" + number);
        return number * 2;
    }
}

結果

第一階段start:6
第二階段start:5
第二階段end:5
最快結果:5

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

CompletableFuture<Integer> future1 = CompletableFuture
        .supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                int number = new Random().nextInt(10) + 1;
                try {
                    TimeUnit.SECONDS.sleep(number);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("第一階段:" + number);
                return number;
            }
        });

CompletableFuture<Integer> future2 = CompletableFuture
        .supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                int number = new Random().nextInt(10) + 1;
                try {
                    TimeUnit.SECONDS.sleep(number);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("第二階段:" + number);
                return number;
            }
        });

future1.acceptEither(future2, new Consumer<Integer>() {
    @Override
    public void accept(Integer number) {
        System.out.println("最快結果:" + number);
    }
}

結果

第二階段:3
最快結果:3

runAfterEither

兩個線程任務相比較,有任何一個執行完成,就進行下一步操作,不關心運行結果。

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

CompletableFuture<Integer> future1 = CompletableFuture
        .supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                int number = new Random().nextInt(5);
                try {
                    TimeUnit.SECONDS.sleep(number);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("第一階段:" + number);
                return number;
            }
        });

CompletableFuture<Integer> future2 = CompletableFuture
        .supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                int number = new Random().nextInt(5);
                try {
                    TimeUnit.SECONDS.sleep(number);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("第二階段:" + number);
                return number;
            }
        });

future1.runAfterEither(future2, new Runnable() {
    @Override
    public void run() {
        System.out.println("已經有一個任務完成了");
    }
}).join();

結果

第一階段:3
已經有一個任務完成了

runAfterBoth

兩個線程任務相比較,兩個全部執行完成,才進行下一步操作,不關心運行結果。

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

CompletableFuture<Integer> future1 = CompletableFuture
        .supplyAsync(new Supplier<Integer>() {
    @Override
    public Integer get() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("第一階段:1");
        return 1;
    }
});

CompletableFuture<Integer> future2 = CompletableFuture
        .supplyAsync(new Supplier<Integer>() {
    @Override
    public Integer get() {
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("第二階段:2");
        return 2;
    }
});

future1.runAfterBoth(future2, new Runnable() {
    @Override
    public void run() {
        System.out.println("上面兩個任務都執行完成了。");
    }
}

結果

第一階段:1
第二階段:2
上面兩個任務都執行完成了。

anyOf

anyOf 方法的參數是多個給定的 CompletableFuture,當其中的任何一個完成時,方法返回這個 CompletableFuture。

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

Random random = new Random();
CompletableFuture<String> future1 = CompletableFuture
        .supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(random.nextInt(5));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "hello";
        });

CompletableFuture<String> future2 = CompletableFuture
        .supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(random.nextInt(1));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "world";
        });
CompletableFuture<Object> result = CompletableFuture.anyOf(future1, future2);

結果

world

allOf

allOf方法用來實現多 CompletableFuture 的同時返回。

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

CompletableFuture<String> future1 = CompletableFuture
        .supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("future1完成!");
            return "future1完成!";
        });

CompletableFuture<String> future2 = CompletableFuture
        .supplyAsync(() -> {
            System.out.println("future2完成!");
            return "future2完成!";
        });

CompletableFuture<Void> combindFuture = CompletableFuture
        .allOf(future1, future2);
try {
    combindFuture.get();
} catch (InterruptedException e) {
    e.printStackTrace();
} catch (ExecutionException e) {
    e.printStackTrace();
}

結果

future2完成!
future1完成!
future1: true,future2: true

CompletableFuture常用方法總結

image

使用案例:實現最優的“燒水泡茶”程序

著名數學家華羅庚先生在《統籌方法》這篇文章裏介紹了一個燒水泡茶的例子,文中提到最優的工序應該是下面這樣:

image

對於燒水泡茶這個程序,一種最優的分工方案:用兩個線程 T1 和 T2 來完成燒水泡茶程序,T1 負責洗水壺、燒開水、泡茶這三道工序,T2 負責洗茶壺、洗茶杯、拿茶葉三道工序,其中 T1 在執行泡茶這道工序時需要等待 T2 完成拿茶葉的工序。

基於Future實現

public class FutureTaskDemo3{

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 創建任務T2的FutureTask
        FutureTask<String> ft2 = new FutureTask<>(new T2Task());
        // 創建任務T1的FutureTask
        FutureTask<String> ft1 = new FutureTask<>(new T1Task(ft2));

        // 線程T1執行任務ft1
        Thread T1 = new Thread(ft1);
        T1.start();
        // 線程T2執行任務ft2
        Thread T2 = new Thread(ft2);
        T2.start();
        // 等待線程T1執行結果
        System.out.println(ft1.get());

    }
}

// T1Task需要執行的任務:
// 洗水壺、燒開水、泡茶
class T1Task implements Callable<String> {
    FutureTask<String> ft2;
    // T1任務需要T2任務的FutureTask
    T1Task(FutureTask<String> ft2){
        this.ft2 = ft2;
    }
    @Override
    public String call() throws Exception {
        System.out.println("T1:洗水壺...");
        TimeUnit.SECONDS.sleep(1);

        System.out.println("T1:燒開水...");
        TimeUnit.SECONDS.sleep(15);
        // 獲取T2線程的茶葉
        String tf = ft2.get();
        System.out.println("T1:拿到茶葉:"+tf);

        System.out.println("T1:泡茶...");
        return "上茶:" + tf;
    }
}
// T2Task需要執行的任務:
// 洗茶壺、洗茶杯、拿茶葉
class T2Task implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println("T2:洗茶壺...");
        TimeUnit.SECONDS.sleep(1);

        System.out.println("T2:洗茶杯...");
        TimeUnit.SECONDS.sleep(2);

        System.out.println("T2:拿茶葉...");
        TimeUnit.SECONDS.sleep(1);
        return "龍井";
    }
}

基於CompletableFuture實現

public class CompletableFutureDemo2 {

    public static void main(String[] args) {

        //任務1:洗水壺->燒開水
        CompletableFuture<Void> f1 = CompletableFuture
                .runAsync(() -> {
                    System.out.println("T1:洗水壺...");
                    sleep(1, TimeUnit.SECONDS);

                    System.out.println("T1:燒開水...");
                    sleep(15, TimeUnit.SECONDS);
                });
        //任務2:洗茶壺->洗茶杯->拿茶葉
        CompletableFuture<String> f2 = CompletableFuture
                .supplyAsync(() -> {
                    System.out.println("T2:洗茶壺...");
                    sleep(1, TimeUnit.SECONDS);

                    System.out.println("T2:洗茶杯...");
                    sleep(2, TimeUnit.SECONDS);

                    System.out.println("T2:拿茶葉...");
                    sleep(1, TimeUnit.SECONDS);
                    return "龍井";
                });
        //任務3:任務1和任務2完成後執行:泡茶
        CompletableFuture<String> f3 = f1.thenCombine(f2, (__, tf) -> {
                    System.out.println("T1:拿到茶葉:" + tf);
                    System.out.println("T1:泡茶...");
                    return "上茶:" + tf;
                });
        //等待任務3執行結果
        System.out.println(f3.join());
    }

    static void sleep(int t, TimeUnit u){
        try {
            u.sleep(t);
        } catch (InterruptedException e) {
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章