Java8新的異步編程方式 CompletableFuture(一)

2020-12-17 CSDN

一. Future

JDK 5引入了Future模式。Future接口是Java多線程Future模式的實現,在java.util.concurrent包中,可以來進行異步計算。

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

Future的接口很簡單,只有五個方法。

public interface Future {

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的結果。

(Java高級程式設計師)學習交流QQ群:478052716 你在學習Java的過程中或者在工作中遇到什麼問題都可以來群裡提問,阿里Java高級大牛直播講解知識點,分享知識,多年工作經驗的梳理和總結,帶著大家全面、科學地建立自己的技術體系和技術認知!非喜勿進!

二. 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(Suppliersupplier) 使用ForkJoinPool.commonPool()作為它的線程池執行異步代碼,異步操作有返回值 supplyAsync(Supplier 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

相關焦點

  • JAVA中使用CompletableFuture進行異步編程
    CompletableFuture異步任務執行線程池,默認是把異步任務都放在ForkJoinPool中執行。在這種方式中,主線程不會被阻塞,不需要一直等到子線程完成。主線程可以並行的執行其他任務。thenCombine:組合兩個future,獲取兩個future的返回結果,並返回當前任務的返回值thenAcceptBoth:組合兩個future,獲取兩個future任務的返回結果,然後處理任務,沒有返回值。
  • Flutter異步編程-Future
    Future可以說是在Dart異步編程中隨處可見,比如一個網絡請求返回的是Future對象,或者訪問一個SharedPreferences返回一個Future對象等等。異步操作可以讓你的程序在等待一個操作完成時繼續處理其它的工作。而Dart 使用 Future 對象來表示異步操作的結果。
  • 《Java 8 in Action》CompletableFuture:組合式異步編程
    使用Future以異步的方式執行一個耗時的操作:線程可以在ExecutorService以並發方式調用另一個線程執行耗時操作的同時,去執行一些其他的任務。接著,如果你已經運行到沒有異步操作的結果就無法繼續任何有意義的工作時,可以調用它的get方法去獲取操作的結果。
  • Python 3.8異步並發編程
    有效的提高程序執行效率的兩種方法是異步和並發,Golang,node.js之所以可以有很高執行效率主要是他們的協程和異步並發機制。實際上異步和並發是每一種現代語言都在追求的特性,當然Python也不例外,今天我們就講講Python 3中的異步並發編程。
  • Python異步編程模塊asyncio學習
    asyncio是Python3.4引入的一個標準庫,直接內置了對異步IO的支持。asyncio模塊提供了使用協程構建並發應用的工具。它使用一種單線程單進程的的方式實現並發,應用的各個部分彼此合作, 可以顯示的切換任務,一般會在程序阻塞I/O操作的時候發生上下文切換如等待讀寫文件,或者請求網絡。
  • .NET異步編程:使用CPS及yield實現異步
    【IT168 技術】在上一篇文章(.NET中的異步編程:傳統的異步編程)中我們圍觀了傳統的異步編程,感受到了異步編程不是簡單的事情。傳統的異步方式將本來緊湊的代碼都分成兩部分,不僅僅降低了代碼的可讀性,還讓一些基本的程序構造無法使用,所以大部分開發人員在遇到應該使用異步的地方都忍痛割愛。
  • java8的函數式編程解析
    其實在java8就已經有java的函數式編程寫法,只是難度較大,大家都習慣了對象式用法,但在其它語言中都有函數式的用法,如js,scala,函數式其實是抽象到極致的思想。什麼是函數式編程 函數式編程並不是Java新提出的概念,其與指令編程相比,強調函數的計算比指令的計算更重要;與過程化編程相比,其中函數的計算可以隨時調用。
  • JAVA並發編程:線程並發工具類Callable、Future 和FutureTask的使用
    FutureTask,可取消的異步計算。此類提供{@link Future}的基本實現,其中包含啟動和取消計算,查詢以查看計算是否完成以及檢索計算結果的方法。只有在計算完成後才能檢索結果;如果計算尚未完成,則{@code get}方法將阻塞。一旦計算完成,就不能重新開始或取消計算(除非使用{@link #runAndReset}調用計算)。UML類圖如下所示,可清晰看到幾個類的關係。
  • 從promise讀懂JavaScript異步編程
    從《setTimeout(fn,0)函數剖析JavaScript的執行機制》一文已經說明了同步和異步的區別,而這篇文章將更深入的去理解什麼是JS的異步編程,以及promise,async,await等的使用。從而更好的為前端編程打好堅實的基礎。
  • Python協程與異步編程超全總結
    Python中異步IO操作是通過asyncio來實現的。異步IO異步IO的asyncio庫使用事件循環驅動的協程實現並發。用戶可主動控制程序,在認為耗時IO處添加await(yield from)。傳入的參數是future或協程構成的可迭代對象。
  • JavaScript 異步編程的終極演變
    以下是《JavaScript 異步編程的終極演變》內容。寫在前面有一個有趣的問題:為什麼Node.js約定回調函數的第一個參數必須是錯誤對象err(如果沒有錯誤,該參數就是null)?原因是執行回調函數對應的異步操作,它的執行分成兩段,這兩段之間拋出的錯誤程序無法捕獲,所以只能作為參數傳入第二段。
  • JavaScript異步編程助手:Promise模式
    異步模式在Web編程中變得越來越重要,對於Web主流語言JavaScript來說,這種模式實現起來不是很利索,為此,許多JavaScript庫(比如 jQuery和Dojo、AngularJS)添加了一種稱為Promise的抽象(術語稱作Deferred模式)。
  • JS異步編程,回調函數與promise
    Promise 是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。
  • Javascript異步編程的4種方法
    「異步模式」非常重要。在瀏覽器端,耗時很長的操作都應該異步執行,避免瀏覽器失去響應,最好的例子就是Ajax操作。在伺服器端,「異步模式」甚至是唯一的模式,因為執行環境是單線程的,如果允許同步執行所有http請求,伺服器性能會急劇下降,很快就會失去響應。
  • 大前端進擊之路|JavaScript異步編程
    異步模式上個例子中我們在等待水燒開的過程中什麼都沒幹,很浪費時間,我們可以在燒水的過程中將食材都準備好,等到水燒開後直接放入。我們在燒水的過程中去幹了別的事情,就屬於異步模式,異步模式中不會等待異步任務的結束才開始執行下一個同步的任務,都是開啟過後就立即執行下一個任務。
  • .NET異步編程:IO完成埠與BeginRead
    【IT168 專稿】寫這個系列原本的想法是討論一下.NET中異步編程風格的變化,特別是F#中的異步工作流以及未來的.NET 5.0中的基於任務的異步編程模型。但經過前幾篇文章(為什麼需要異步,傳統的異步編程,使用CPS及yield實現異步)的發表後,很多人對IO異步背後實現的原理以及為什麼這樣能提高性能很感興趣。
  • 大前端進擊之路(二):JavaScript異步編程
    異步模式上個例子中我們在等待水燒開的過程中什麼都沒幹,很浪費時間,我們可以在燒水的過程中將食材都準備好,等到水燒開後直接放入。我們在燒水的過程中去幹了別的事情,就屬於異步模式,異步模式中不會等待異步任務的結束才開始執行下一個同步的任務,都是開啟過後就立即執行下一個任務。
  • JavaScript異步編程之jsdeferred原理解析
    前言 最近在看司徒正美的《JavaScript框架設計》,看到異步編程的那一章介紹了jsdeferred這個庫,覺得很有意思,花了幾天的時間研究了一下代碼,在此做一下分享。 異步編程是編寫js的一個很重要的理念,特別是在處理複雜應用的時候,異步編程的技巧就至關重要。那麼下面就來看看這個被稱為裡程碑式的異步編程庫吧。 2.
  • python異步編程模塊asyncio學習(二)
    在上次我們說如何使用asyncio,詳情點擊:python異步編程模塊asyncio學習(一)。接下來我們繼續學習關於異步模塊asyncio的其他操作。儘管asyncio應用通常作為單線程運行,不過仍被構建為並發應用。由於I/O以及其他外部事件的延遲和中斷,每個協程或任務可能按一種不可預知的順序執行。
  • 程式設計師請注意:異步編程模式已被人註冊為專利
    不論是伺服器端編程還是客戶端編程,編程中的同步和異步對程式設計師來說都應該不陌生,我們經常會用同步編程來解決順序執行問題、用異步解決並行執行問題