日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

【Java并发编程实战】(十七):Future和CompletableFuture的原理及实战——异步编程没有那么难

發布時間:2023/12/14 java 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【Java并发编程实战】(十七):Future和CompletableFuture的原理及实战——异步编程没有那么难 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 引言
  • 生活中的例子
    • 場景1
    • 場景2
  • Java中的Future
    • 如何獲取Future
    • Future的主要方法及使用
    • Future的核心源碼
  • Future模式的高階版本—— CompletableFuture
    • 如何獲取CompletableFuture
    • CompletableFuture的主要方法及使用
  • 小結

引言

在高性能編程中,并發編程已經成為了極為重要的一部分。并發編程可以總結為三個核心問題:分工、同步和互斥。編寫并發程序,首先要做的就是分工,所謂分工指的是如何高效地拆解任務并分配給線程。由于并發編程比串行編程更困難,也更容易出錯,因此,我們就更需要借鑒一些前人優秀的,成熟的設計模式,使得我們的設計更加健壯,更加完美。
而Future模式,正是其中使用最為廣泛,也是極為重要的一種設計模式。今天就跟少俠了解一手Future模式!

生活中的例子

場景1

小張喜歡沒事泡泡茶,每次都是洗水壺–>洗茶壺–>洗茶杯–>燒開水–>拿茶葉–>泡茶,如下圖,喝到茶大概得花上20分鐘。

場景2

但是小王不這么干,對于燒水泡茶這個程序,她采取的方案是下圖所示的這樣:用兩個線程T1和T2來完成燒水泡茶程序,T1負責洗水壺、燒開水、泡茶這三道工序,T2負責洗茶壺、洗茶杯、拿茶葉三道工序,其中T1在執行泡茶這道工序時需要等待T2完成拿茶葉的工序。對于T1的這個等待動作,你應該可以想出很多種辦法,例如Thread.join()、CountDownLatch,甚至阻塞隊列都可以解決,不過今天我們用Future特性來實現。

Java中的Future

如何獲取Future

// 提交Runnable任務 Future submit(Runnable task);

這個方法的參數是一個Runnable接口,Runnable接口的run()方法是沒有返回值的,所以 submit(Runnable task) 這個方法返回的Future僅可以用來斷言任務已經結束了,類似于Thread.join()。

// 提交Callable任務Future submit(Callable task);

這個方法的參數是一個Callable接口,它只有一個call()方法,并且這個方法是有返回值的,所以這個方法返回的Future對象可以通過調用其get()方法來獲取任務的執行結果。

// 提交Runnable任務及結果引用 Future submit(Runnable task, T result);

這個方法很有意思,假設這個方法返回的Future對象是future,future.get()的返回值就是傳給submit()方法的參數result。這個方法該怎么用呢?下面這段示例代碼展示了它的經典用法。需要你注意的是Runnable接口的實現類Task聲明了一個有參構造函數 Task(Result r) ,創建Task對象的時候傳入了result對象,這樣就能在類Task的run()方法中對result進行各種操作了。result相當于主線程和子線程之間的橋梁,通過它主子線程可以共享數據。

Future的主要方法及使用

獲取到Future之后,我們怎么來進行使用呢,Java中提供了如下幾個核心方法:

// 取消任務boolean cancel(boolean mayInterruptIfRunning);// 判斷任務是否已取消 boolean isCancelled();// 判斷任務是否已結束boolean isDone();// 獲得任務執行結果get();// 獲得任務執行結果,支持超時get(long timeout, TimeUnit unit);

那么如何靈活的使用這幾個方法呢?下面的示例代碼就是用這一節提到的Future特性來實現的。首先,我們創建了兩個Future——task1和task2,task1完成洗水壺、燒開水、泡茶的任務,task2完成洗茶壺、洗茶杯、拿茶葉的任務;這里需要注意的是task1這個任務在執行泡茶任務前,需要等待task2把茶葉拿來,所以task1內部需要引用task2,并在執行泡茶之前,調用task2的get()方法實現等待。

一次執行結果:

thread13--->洗茶壺 thread14--->洗水壺 thread13--->洗茶杯 thread14--->燒開水 thread13--->拿茶葉 thread14--->泡茶 泡茶吧!來一杯大紅袍! elapsed: 16

Future的核心源碼

那么Future又是如何實現異步操作的呢,我們結合源碼來看一下。

由于Future是接口,這里我們主要看它的實現類FutureTask的實現。關鍵的部分在下面,FutureTask作為一個線程單獨執行時,會將結果保存到Object類型的變量outcome中,并設置任務的狀態,下面是FutureTask的run()方法:

從FutureTask中獲得結果的實現如下:

Future模式的高階版本—— CompletableFuture

Future模式雖然好用,但也有一個問題,那就是將任務提交給線程后,調用線程并不知道這個任務什么時候執行完,如果執行調用get()方法或者isDone()方法判斷,可能會進行不必要的等待,那么系統的吞吐量很難提高。
為了解決這個問題,JDK對Future模式又進行了加強,創建了一個CompletableFuture,它可以理解為Future模式的升級版本,它最大的作用是提供了一個回調機制,可以在任務完成后,自動回調一些后續的處理,這樣,整個程序可以把“結果等待”完全給移除了。

如何獲取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)

四個靜態方法用來為一段異步執行的代碼創建CompletableFuture對象,方法的參數類型都是函數式接口,所以可以使用lambda表達式實現異步任務

  • runAsync方法:它以Runnable函數式接口類型為參數,所以CompletableFuture的計算結果為空。

  • supplyAsync方法以Supplier函數式接口類型為參數,CompletableFuture的計算結果類型為U。

說明:Async結尾的方法都是可以異步執行的,如果指定了線程池,會在指定的線程池中執行,如果沒有指定,默認會在ForkJoinPool.commonPool()中執行。

CompletableFuture的主要方法及使用

關于CompletableFuture,Java中提供了如下幾個核心方法:
1 變換結果
由于回調風格的實現,我們不必因為等待一個計算完成而阻塞著調用線程,而是告訴CompletableFuture當計算完成的時候請執行某個Function。還可以串聯起來。
這些方法的輸入是上一個階段計算后的結果,返回值是經過轉化后結果:

public <U> CompletionStage<U> thenApply(Function<? super T,? extends U> fn); public <U> CompletionStage<U> thenApplyAsync(Function<? super T,? extends U> fn); public <U> CompletionStage<U> thenApplyAsync(Function<? super T,? extends U> fn,Executor executor);

2 消費結果
這些方法只是針對結果進行消費,入參是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);

3 計算結果完成時的處理
當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)

4 結合兩個CompletionStage的結果,進行轉化后返回
需要上一階段的返回值,并且other代表的CompletionStage也要返回值之后,把這兩個返回值,進行轉換后返回指定類型的值。

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

為了和大家一起體會CompletableFuture異步編程的優勢,這里我們用CompletableFuture重新實現前面曾提及的燒水泡茶程序。首先還是需要先完成分工方案,在下面的程序中,我們分了3個任務:任務1負責洗水壺、燒開水,任務2負責洗茶壺、洗茶杯和拿茶葉,任務3負責泡茶。其中任務3要等待任務1和任務2都完成后才能開始。這個分工如下圖所示。

下面是具體代碼實現,你先略過runAsync()、supplyAsync()、thenCombine()這些不太熟悉的方法,從大局上看,你會發現:

  • 無需手工維護線程,沒有繁瑣的手工維護線程的工作,給任務分配線程的工作也不需要我們關注;
  • 語義更清晰,例如 f3 = f1.thenCombine(f2, ()->{}) 能夠清晰地表述“任務3要等待任務1和任務2都完成后才能開始”;
  • 代碼更簡練并且專注于業務邏輯,幾乎所有代碼都是業務邏輯相關的。

小結

今天我們主要介紹Future模式,我們從一個最簡單的Future模式開始,逐步深入,先后介紹了JDK內部的Future模式實現,以及對Future模式的進化版本CompletableFuture做了簡單的介紹。對
于多線程開發而言,Future模式的應用極其廣泛,可以說這個模式已經成為了異步開發的基礎設施。

總結

以上是生活随笔為你收集整理的【Java并发编程实战】(十七):Future和CompletableFuture的原理及实战——异步编程没有那么难的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。