Java8新的非同步程式設計方式 CompletableFuture(一)

Tony沈哲發表於2017-10-21

一. Future

JDK 5引入了Future模式。Future介面是Java多執行緒Future模式的實現,在java.util.concurrent包中,可以來進行非同步計算。

Future模式是多執行緒設計常用的一種設計模式。Future模式可以理解成:我有一個任務,提交給了Future,Future替我完成這個任務。期間我自己可以去做任何想做的事情。一段時間之後,我就便可以從Future那兒取出結果。

Future的介面很簡單,只有五個方法。

public interface Future<V> {

    boolean cancel(boolean mayInterruptIfRunning);

    boolean isCancelled();

    boolean isDone();

    V get() throws InterruptedException, ExecutionException;

    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}複製程式碼

Future介面的方法介紹如下:

  • 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

一般情況下,我們會結合Callable和Future一起使用,通過ExecutorService的submit方法執行Callable,並返回Future。

        ExecutorService executor = Executors.newCachedThreadPool();

        Future<String> future = executor.submit(() -> { //Lambda 是一個 callable, 提交後便立即執行,這裡返回的是 FutureTask 例項
            System.out.println("running task");
            Thread.sleep(10000);
            return "return task";
        });

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
        }

        System.out.println("do something else");  //前面的的 Callable 在其他執行緒中執行著,可以做一些其他的事情

        try {
            System.out.println(future.get());  //等待 future 的執行結果,執行完畢之後列印出來
        } catch (InterruptedException e) {
        } catch (ExecutionException e) {

        } finally {
            executor.shutdown();
        }複製程式碼

比起future.get(),其實更推薦使用get (long timeout, TimeUnit unit) 方法,設定了超時時間可以防止程式無限制的等待future的結果。

二. CompletableFuture介紹

2.1 Future模式的缺點

  • Future雖然可以實現獲取非同步執行結果的需求,但是它沒有提供通知的機制,我們無法得知Future什麼時候完成。

  • 要麼使用阻塞,在future.get()的地方等待future返回的結果,這時又變成同步操作。要麼使用isDone()輪詢地判斷Future是否完成,這樣會耗費CPU的資源。

2.2 CompletableFuture介紹

Netty、Guava分別擴充套件了Java 的 Future 介面,方便非同步程式設計。

Java 8新增的CompletableFuture類正是吸收了所有Google Guava中ListenableFuture和SettableFuture的特徵,還提供了其它強大的功能,讓Java擁有了完整的非阻塞程式設計模型:Future、Promise 和 Callback(在Java8之前,只有無Callback 的Future)。

CompletableFuture能夠將回撥放到與任務不同的執行緒中執行,也能將回撥作為繼續執行的同步函式,在與任務相同的執行緒中執行。它避免了傳統回撥最大的問題,那就是能夠將控制流分離到不同的事件處理器中。

CompletableFuture彌補了Future模式的缺點。在非同步的任務完成後,需要用其結果繼續操作時,無需等待。可以直接通過thenAccept、thenApply、thenCompose等方式將前面非同步處理的結果交給另外一個非同步事件處理執行緒來處理。

三. CompletableFuture特性

3.1 CompletableFuture的靜態工廠方法

方法名 描述
runAsync(Runnable runnable) 使用ForkJoinPool.commonPool()作為它的執行緒池執行非同步程式碼。
runAsync(Runnable runnable, Executor executor) 使用指定的thread pool執行非同步程式碼。
supplyAsync(Supplier<U> supplier) 使用ForkJoinPool.commonPool()作為它的執行緒池執行非同步程式碼,非同步操作有返回值
supplyAsync(Supplier<U> supplier, Executor executor) 使用指定的thread pool執行非同步程式碼,非同步操作有返回值

runAsync 和 supplyAsync 方法的區別是runAsync返回的CompletableFuture是沒有返回值的。

        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            System.out.println("Hello");
        });

        try {
            future.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        System.out.println("CompletableFuture");複製程式碼

而supplyAsync返回的CompletableFuture是由返回值的,下面的程式碼列印了future的返回值。

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

        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        System.out.println("CompletableFuture");複製程式碼

3.2 Completable

方法名 描述
complete(T t) 完成非同步執行,並返回future的結果
completeExceptionally(Throwable ex) 非同步執行不正常的結束

future.get()在等待執行結果時,程式會一直block,如果此時呼叫complete(T t)會立即執行。

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

        future.complete("World");

        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }複製程式碼

執行結果:

World複製程式碼

可以看到future呼叫complete(T t)會立即執行。但是complete(T t)只能呼叫一次,後續的重複呼叫會失效。

如果future已經執行完畢能夠返回結果,此時再呼叫complete(T t)則會無效。

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

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        future.complete("World");

        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }複製程式碼

執行結果:

Hello複製程式碼

如果使用completeExceptionally(Throwable ex)則丟擲一個異常,而不是一個成功的結果。

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

        future.completeExceptionally(new Exception());

        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }複製程式碼

執行結果:

java.util.concurrent.ExecutionException: java.lang.Exception
...複製程式碼

相關文章