【Java多線程-5】 CompletableFuture詳解


Future 是Java 5添加的類,用來描述一個異步計算的結果。前文中,我們領略了 Future 的便利,但它還是存在諸多不足,比如:

  • Future 對於結果的獲取很不方便,只能通過阻塞或者輪詢的方式得到任務的結果。阻塞的方式顯然是效率低下的,輪詢的方式又十分耗費CPU資源,而且也不能保證實時得到計算結果。
  • Future難以解決線程執行結果之間的依賴關係,比如一個線程等待另一個線程執行結束再執行,以及兩個線程執行結果的合併處理等。

Java8帶來了 CompletableFuture,CompletableFuture類實現了CompletionStage和Future接口,提供了非常強大的Future的擴展功能,可以幫助我們簡化異步編程的複雜性,提供了函數式編程的能力,可以通過回調的方式處理計算結果,並且提供了轉換和組合CompletableFuture的方法。

1 創建對象

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;
  • 沒有指定Executor的方法會使用ForkJoinPool.commonPool() 作爲它的線程池執行異步代碼。如果指定線程池,則使用指定的線程池運行。

ForkJoinPool是JDK7提供的,叫做分支/合併框架。可以通過將一個任務遞歸分成很多分子任務,形成不同的流,進行並行執行,同時還伴隨着強大的工作竊取算法。極大的提高效率。

示例:

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

/**
 - @author guozhengMu
 - @version 1.0
 - @date 2019/11/7 17:44
 - @description
 - @modify
 */
public class CompletableFutureTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Runnable runnable = () ->
                System.out.println("執行無返回結果的異步任務");
        System.out.println(CompletableFuture.runAsync(runnable).get());

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

2 結果處理

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

示例:

public static void main(String[] args) {
    CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {

        }
        if (new Random().nextInt() % 2 == 0) {
            int i = 12 / 0;
        }
        System.out.println("執行結束!");
    });

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

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

下面一組方法雖然也返回CompletableFuture對象,但是對象的值和原來的CompletableFuture計算的值不同。當原先的CompletableFuture的值計算完成或者拋出異常的時候,會觸發這個CompletableFuture對象的計算,結果由BiFunction參數計算而得。因此這組方法兼有whenComplete和轉換的兩個功能。

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)

3 結果轉換

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

3.1 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)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)

示例:

public static void main(String[] args) throws ExecutionException, InterruptedException {
    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;
    });

    System.out.println("最終結果:" + future.get());
}

3.2 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) ;
public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn, Executor executor) ;

示例:

public static void main(String[] args) throws InterruptedException, ExecutionException {
    CompletableFuture<Integer> future = CompletableFuture.supplyAsync(new Supplier<Integer>() {
        @Override
        public Integer get() {
            int number = new Random().nextInt(3);
            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;
                }
            });
        }
    });
    System.out.println("最終結果: " + future.get());
}

那麼 thenApply 和 thenCompose 有何區別呢:

  • thenApply 轉換的是泛型中的類型,返回的是同一個CompletableFuture;
  • thenCompose 將內部的 CompletableFuture 調用展開來並使用上一個CompletableFutre 調用的結果在下一步的 CompletableFuture 調用中進行運算,是生成一個新的CompletableFuture。

下面用一個例子對對比:

public static void main(String[] args) throws InterruptedException, ExecutionException {
    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());
    System.out.println(result2.get());
}

4 結果消費

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

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

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

4.1 thenAccept

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

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 static void main(String[] args) throws ExecutionException, InterruptedException {
    CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
        int number = new Random().nextInt(10);
        System.out.println("第一階段:" + number);
        return number;
    }).thenAccept(number ->
            System.out.println("第二階段:" + number * 5));
    System.out.println("最終結果:" + future.get());
}

4.2 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);
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action,     Executor executor);

示例:

public static void main(String[] args) throws ExecutionException, InterruptedException {
    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));
        }
    }).join();
}

4.3 thenRun

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

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

示例:

public static void main(String[] args) throws ExecutionException, InterruptedException {
    CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
        int number = new Random().nextInt(10);
        System.out.println("第一階段:" + number);
        return number;
    }).thenRun(() ->
            System.out.println("thenRun 執行"));
    System.out.println("最終結果:" + future.get());
}

5 結果組合

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);
public <U,V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn,Executor executor);

示例:

public static void main(String[] args) throws ExecutionException, InterruptedException {
    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;
        }
    });
    System.out.println("最終結果:" + result.get());
}

6 任務交互

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

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

示例:

public static void main(String[] args) throws ExecutionException, InterruptedException {
    CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
        @Override
        public Integer get() {
            int number = new Random().nextInt(3);
            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);
            try {
                TimeUnit.SECONDS.sleep(number);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("第二階段:" + number);
            return number;
        }
    });

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

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

示例:

public static void main(String[] args) {
    CompletableFuture<Integer> future1 = 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;
        }
    });

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

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

示例:

import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 * @author guozhengMu
 * @version 1.0
 * @date 2019/11/7 22:44
 * @description
 * @modify
 */
public class CompletableFutureTest {
    public static void main(String[] args) {
        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();
    }
}

6.4 runAfterBoth

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

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

示例:

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 * @author guozhengMu
 * @version 1.0
 * @date 2019/11/7 22:44
 * @description
 * @modify
 */
public class CompletableFutureTest {
    public static void main(String[] args) {
        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("上面兩個任務都執行完成了。");
            }
        }).get();
    }
}

6.5 anyOf

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

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

示例:

public static void main(String[] args) throws ExecutionException, InterruptedException {
    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);
    System.out.println(result.get());
}

6.6 allOf

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

示例:

public static void main(String[] args) {
    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();
    }
    System.out.println("future1: " + future1.isDone() + ",future2: " + future2.isDone());
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章