亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

學(xué)會(huì)CompletableFuture輕松駕馭異步編程

 更新時(shí)間:2023年04月10日 14:58:39   作者:leobert-lan  
這篇文章主要為大家介紹了CompletableFuture輕松駕馭異步編程教程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

本文隸屬于我歸納整理的Android知識體系的第四部分,屬于 異步 部分的多線程內(nèi)容

您可以通過訪問 總綱 閱讀系列內(nèi)的其他文章。

作者按:草稿進(jìn)行了幾次大改,移除了Demo部分、源碼解析部分、設(shè)計(jì)原理部分。結(jié)合實(shí)際工作經(jīng)驗(yàn),"掌握API能熟練使用、能無障礙閱讀相關(guān)框架源碼" 已基本夠用。

讀者可結(jié)合下面的導(dǎo)圖進(jìn)行快速的知識自查

一個(gè)美好的期望

通常情況下,我們希望代碼的執(zhí)行順序和代碼的組織順序一致,即代碼表述了同步執(zhí)行的程序,這樣可以減少很多思考。

而 閱讀異步的程序代碼,需要在腦海中建立事件流,當(dāng)程序業(yè)務(wù)復(fù)雜時(shí),將挑戰(zhàn)人的記憶力和空間想象力,并非所有人都擅長在腦海中構(gòu)建并分析異步事件流模型。

所以,我們期望擁有一個(gè)非常友好的框架,能夠讓我們方便地進(jìn)行異步編程,并且在框架內(nèi)部設(shè)計(jì)有線程同步、異常處理機(jī)制。

并且,基于該框架編寫的代碼具有很高的可讀、可理解性。

而Future基本無法滿足這一期望。

Future的不足與CompletableFuture的來源

Future的不足

在先前的系列文章中,我們已經(jīng)回顧了Future類的設(shè)計(jì),在絕大多數(shù)場景下,我們選擇使用多線程,是為了 充分利用機(jī)器性能 以及 避免用戶交互線程出現(xiàn)長時(shí)間阻塞 以致影響體驗(yàn)。

所以我們將耗時(shí)的、會(huì)引起長時(shí)間阻塞的任務(wù)分離到其他線程執(zhí)行,并在 合適時(shí)機(jī) 進(jìn)行線程同步,于主線程(一般負(fù)責(zé)用戶交互處理、界面渲染)中處理結(jié)果。

詳見拙作 掌握Future,輕松獲取異步任務(wù)結(jié)果

Future 于 Java 1.5版本引入,它類似于 異步處理的結(jié)果占位符 , 提供了兩個(gè)方法獲取結(jié)果:

  • get(), 調(diào)用線程進(jìn)入阻塞直至得到結(jié)果或者異常。
  • get(long timeout, TimeUnit unit), 調(diào)用線程將僅在指定時(shí)間 timeout 內(nèi)等待結(jié)果或者異常,如果超時(shí)未獲得結(jié)果就會(huì)拋出 TimeoutException 異常。

Future 可以實(shí)現(xiàn) RunnableCallable 接口來定義任務(wù),一定程度上滿足 使用框架進(jìn)行異步編程 的期望,但通過整體源碼可知它存在如下 3個(gè)問題 :

  • 調(diào)用 get() 方法會(huì)一直阻塞直到獲取結(jié)果、異常,無法在任務(wù)完成時(shí)獲得 "通知" ,無法附加回調(diào)函數(shù)
  • 不具備鏈?zhǔn)秸{(diào)用和結(jié)果聚合處理能力,當(dāng)我們想鏈接多個(gè) Future 共同完成一件任務(wù)時(shí),沒有框架級的處理,只能編寫業(yè)務(wù)級邏輯,合并結(jié)果,并小心的處理同步
  • 需要單獨(dú)編寫異常處理代碼

使用 get(long timeout, TimeUnit unit)isDone() 判斷,確實(shí)可以緩解問題1,但這需要結(jié)合業(yè)務(wù)單獨(dú)設(shè)計(jì)(調(diào)優(yōu)),存在大量的不確定性。不再展開

Java 8中引入 CompletableFuture 來解決 Future 的不足。

CompletableFuture來源

CompletableFuture 的設(shè)計(jì)靈感來自于 Google Guava 庫的 ListenableFuture 類,它實(shí)現(xiàn)了 Future接口CompletionStage接口 , 并且新增一系列API,支持Java 8的 lambda特性,通過回調(diào)利用非阻塞方法,提升了異步編程模型。

它解決了Future的不足,允許我們在非主線程中運(yùn)行任務(wù),并向啟動(dòng)線程 (一般是主線程) 通知 任務(wù)完成任務(wù)失敗,編寫異步的、非阻塞的程序。

使用CompletableFuture

最簡方式獲取實(shí)例

使用 CompletableFuture.completedFuture(U value) 可以獲取一個(gè) 執(zhí)行狀態(tài)已經(jīng)完成CompletableFuture 對象。

這可以用于快速改造舊程序,并進(jìn)行逐步過渡

class Demo {
    @Test
    public void testSimpleCompletableFuture() {
        CompletableFuture<String> completableFuture =
                CompletableFuture.completedFuture("testSimpleCompletableFuture");
        assertTrue(completableFuture.isDone());
        try {
            assertEquals("testSimpleCompletableFuture", completableFuture.get());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

改造線程同步部分

部分老舊程序已經(jīng)建立了多線程業(yè)務(wù)模型,我們可以使用 CompletableFuture 改造其中的線程同步部分,但暫不改造數(shù)據(jù)傳遞。

使用 runAsync() 方法,該方法接收一個(gè) Runnable 類型的參數(shù)返回 CompletableFuture<Void>:

//并不改變原項(xiàng)目中數(shù)據(jù)傳遞的部分、或者不關(guān)心結(jié)果數(shù)據(jù),僅進(jìn)行同步
class Demo {
    @Test
    public void testCompletableFutureRunAsync() {
        AtomicInteger variable = new AtomicInteger(0);
        CompletableFuture<Void> runAsync = CompletableFuture.runAsync(() -> process(variable));
        runAsync.join();
        assertEquals(1, variable.get());
    }
    public void process(AtomicInteger variable) {
        System.out.println(Thread.currentThread() + " Process...");
        variable.set(1);
    }
}

進(jìn)一步改造結(jié)果數(shù)據(jù)傳遞

當(dāng)我們關(guān)心異步任務(wù)的結(jié)果數(shù)據(jù)、或者改造原 多線程業(yè)務(wù)模型 的 數(shù)據(jù)傳遞方式 時(shí),可以使用 supplyAsync() 方法,該方法接收一個(gè) Supplier<T> 接口類型的參數(shù),它實(shí)現(xiàn)了任務(wù)的邏輯,方法返回 CompletableFuture<T> 實(shí)例。

class Demo {
    @Test
    public void testCompletableFutureSupplyAsync() {
        CompletableFuture<String> supplyAsync =
                CompletableFuture.supplyAsync(this::process);
        try {
            // Blocking 
            assertEquals("testCompletableFutureSupplyAsync", supplyAsync.get());
        } catch (ExecutionException | InterruptedException e) {
            e.printStackTrace();
        }
    }
    public String process() {
        return "testCompletableFutureSupplyAsync";
    }
}

指定執(zhí)行線程池

"獲取用于執(zhí)行任務(wù)的線程" 類似 Java 8 中的 parallelStreamCompletableFuture 默認(rèn)從全局 ForkJoinPool.commonPool() 獲取線程,用于執(zhí)行任務(wù)。同時(shí)也提供了指定線程池的方式用于獲取線程執(zhí)行任務(wù),您可以使用API中具有 Executor 參數(shù)的重載方法。

class Demo {
    @Test
    public void testCompletableFutureSupplyAsyncWithExecutor() {
        ExecutorService newFixedThreadPool =
                Executors.newFixedThreadPool(2);
        CompletableFuture<String> supplyAsync = CompletableFuture.supplyAsync(this::process,
                newFixedThreadPool);
        try {
            // Blocking 
            assertEquals("testCompletableFutureSupplyAsyncWithExecutor", supplyAsync.get());
        } catch (ExecutionException | InterruptedException e) {
            e.printStackTrace();
        }
    }
    public String process() {
        return "testCompletableFutureSupplyAsyncWithExecutor";
    }
}

CompletableFuture 中有眾多API,方法命名中含有 Async 的API可使用線程池。

截至此處,以上使用方式均與 Future 類似,接下來演示 CompletableFuture 的不同

回調(diào)&鏈?zhǔn)秸{(diào)用

CompletableFutureget()API是阻塞式獲取結(jié)果,CompletableFuture 提供了

  • thenApply
  • thenAccept
  • thenRun

等API來避免阻塞式獲取,并且可添加 任務(wù)完成 后的回調(diào)。這幾個(gè)方法的使用場景如下:

  • <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn) 收到結(jié)果后,可以進(jìn)行轉(zhuǎn)化
  • CompletableFuture<Void> thenAccept(Consumer<? super T> action) 收到結(jié)果后,對其進(jìn)行消費(fèi)
  • CompletableFuture<Void> thenRun(Runnable action) 收到結(jié)果后,執(zhí)行回調(diào),無法消費(fèi)結(jié)果只能消費(fèi) 這一事件

API較為簡單,不再代碼演示

顯然,通過鏈?zhǔn)秸{(diào)用可以組裝多個(gè)執(zhí)行過程。

有讀者可能會(huì)疑惑:FunctionConsumer 也可以進(jìn)行鏈?zhǔn)浇M裝,是否存在冗余呢?

兩種的鏈?zhǔn)秸{(diào)用特性確實(shí)存在重疊,您可以自行選擇用法,但 thenRun 只能采用 CompletableFuture的鏈?zhǔn)秸{(diào)用。

另外,前面提到,我們可以指定線程池執(zhí)行任務(wù),對于這三組API,同樣有相同的特性,通過 thenXXXXAsync 指定線程池,這是 FunctionConsumer 的鏈?zhǔn)浇M裝所無法完成的。

class Demo {
    @Test
    public void testCompletableFutureApplyAsync() {
        ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(2);
        ScheduledExecutorService newSingleThreadScheduledExecutor = Executors.newSingleThreadScheduledExecutor();
        // 從線程池 newFixedThreadPool 獲取線程執(zhí)行任務(wù) 
        CompletableFuture<Double> completableFuture =
                CompletableFuture.supplyAsync(() -> 1D, newFixedThreadPool)
                        .thenApplyAsync(d -> d + 1D, newSingleThreadScheduledExecutor)
                        .thenApplyAsync(d -> d + 2D);
        Double result = completableFuture.join();
        assertEquals(4D, result);
    }
}

聚合多個(gè)CompletableFuture

通過 聚合 多個(gè) CompletableFuture,可以組成更 復(fù)雜 的業(yè)務(wù)流,可以達(dá)到精細(xì)地控制粒度、聚焦單個(gè)節(jié)點(diǎn)的業(yè)務(wù)。

注意:操作符并不能完全的控制 CompletableFuture 任務(wù)執(zhí)行的時(shí)機(jī),您需要謹(jǐn)慎的選擇 CompletableFuture 的創(chuàng)建時(shí)機(jī)

thenCompose、thenComposeAsync

compose 原意為 組成, 通過多個(gè) CompletableFuture 構(gòu)建異步流。

在操作的 CompletableFuture 獲得結(jié)果時(shí),將另一個(gè) CompletableFuture compose 到異步流中,compose的過程中,可以根據(jù)操作的 CompletableFuture 的結(jié)果編寫邏輯。

thenApply 相比,thenCompose 返回邏輯中提供的 CompletableFuturethenApply 返回框架內(nèi)處理的新實(shí)例。

注意,這一特性在使用 FP編程范式進(jìn)行編碼時(shí),會(huì)顯得非常靈活,一定程度上提升了函數(shù)的復(fù)用性

API含義直觀,不再進(jìn)行代碼演示

thenCombine、thenCombineAsync

thenCombine 可以用于合并多個(gè) 獨(dú)立任務(wù) 的處理結(jié)果。

注意: thenCompose 進(jìn)行聚合時(shí),下游可以使用上游的結(jié)果,在業(yè)務(wù)需求上一般表現(xiàn)為依賴上一步結(jié)果,而非兩者相互獨(dú)立。

例如,產(chǎn)品希望在博客詳情頁同時(shí)展示 "博客的詳情" 和 "作者主要信息" ,以避免內(nèi)容區(qū)抖動(dòng)或割裂的骨架占位。這兩者 可以獨(dú)立獲取時(shí) ,則可以使用 thenCombine 系列API,分別獲取,并合并結(jié)果。

combine 的特點(diǎn)是 被合并的兩個(gè) CompletableFuture 可以并發(fā),等兩者都獲得結(jié)果后進(jìn)行合并。

但它依舊存在使用上的不便捷,合并超過2個(gè) CompletableFuture 時(shí),顯得不夠靈活??梢允褂?static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs) API。

allOf 創(chuàng)建了 CompletableFuture<Void>,并不會(huì)幫助我們合并結(jié)果,所以需要自行編寫業(yè)務(wù)代碼合并,故存在 Side Effects。

runAfterBoth、runAfterBothAsync;runAfterEither、runAfterEitherAsync

  • runAfterBoth 系列API在兩個(gè) CompletableFuture 都獲得結(jié)果后執(zhí)行回調(diào)
  • runAfterEither 系列API在兩個(gè) CompletableFuture 任意一個(gè)獲得結(jié)果后執(zhí)行回調(diào)

通過API,不難理解它們需要使用者自行處理結(jié)果

  • CompletableFuture<Void> runAfterBoth(CompletionStage<?> other, Runnable action);
  • CompletableFuture<Void> runAfterEither(CompletionStage<?> other, Runnable action)

同樣可以增加編碼靈活性,不再贅述。

applyToEither、applyToEitherAsync;

acceptEither、acceptEitherAsync;thenAcceptBoth、thenAcceptBothAsync

  • applyToEither 系列API表現(xiàn)如 thenApplyEither 的組合,兩個(gè)同類型的 CompletableFuture 任意一個(gè)獲得結(jié)果后,可消費(fèi)該結(jié)果并進(jìn)行改變,類似 thenApply
  • acceptEither 系列API表現(xiàn)如 thenAcceptEither 的組合,兩個(gè)同類型的 CompletableFuture 任意一個(gè)獲得結(jié)果后,可消費(fèi)該結(jié)果,類似 thenAccept
  • thenAcceptBoth 系列API表現(xiàn)如 thenCombine,但返回 CompletableFuture<Void>

同樣可以增加編碼靈活性,不再贅述

結(jié)果處理

使用回調(diào)處理結(jié)果有兩種API,注意,除了正常獲得結(jié)果外還可能獲得異常,而這兩組API簇差異體現(xiàn)在對 異常 的處理中。

<U> CompletableFuture<U> handle(BiFunction<? super T, Throwable, ? extends U> fn) CompletableFuture<T> whenComplete(BiConsumer<? super T, ? super Throwable> action)

handle 使用 BiFunction,無論是正常結(jié)果還是異常情況,均視作可被邏輯接受,消費(fèi)后轉(zhuǎn)化

whenComplete 使用 BiConsumer,僅可消費(fèi)但不能轉(zhuǎn)化,異常情況被視作不可被邏輯接受,仍會(huì)拋出。

舉個(gè)例子,進(jìn)行網(wǎng)絡(luò)編程時(shí)會(huì)遇到 Exception, 如果業(yè)務(wù)設(shè)計(jì)中使用的模型實(shí)體包含了 正常結(jié)果、異常 兩種情況:

open class Result<T>(val t: T?) {
    open val isThr: Boolean = false
}
class FailResult<T>(val tr: Throwable) : Result<T>(null) {
    override val isThr: Boolean = true
}

則適合使用 handle API在底層處理。否則需要額外的異常處理,可依據(jù)項(xiàng)目的設(shè)計(jì)選擇處理方式,一般在依據(jù)FP范式設(shè)計(jì)的程序中,傾向于使用handle,避免增加side effect。

異常處理

在多線程背景下,異常處理并不容易。它不僅僅是使用 try-catch 捕獲異常,還包含程序異步流中,節(jié)點(diǎn)出現(xiàn)異常時(shí)流的業(yè)務(wù)走向。

CompletableFuture 中,節(jié)點(diǎn)出現(xiàn)異常將跳過后續(xù)節(jié)點(diǎn),進(jìn)入異常處理。

_如果您不希望某個(gè)節(jié)點(diǎn)拋出異常導(dǎo)致后續(xù)流程中斷,則可在節(jié)點(diǎn)的處理中捕獲并包裝為結(jié)果、或者對子 CompletableFuture 節(jié)點(diǎn)采用 handle、exceptionally API轉(zhuǎn)換異常 _

除前文提到的 handle whenComplete,CompletableFuture 中還提供了 exceptionally API用于處理異常

CompletableFuture<T> exceptionally(Function<Throwable, ? extends T> fn)

從表現(xiàn)結(jié)果看,它類似于 handle API中對異常的處理,將異常轉(zhuǎn)換為目標(biāo)結(jié)果的一種特定情形。

以上就是學(xué)會(huì)CompletableFuture輕松駕馭異步編程的詳細(xì)內(nèi)容,更多關(guān)于CompletableFuture異步編程的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Spring?Cloud?Gateway遠(yuǎn)程命令執(zhí)行漏洞分析(CVE-2022-22947)

    Spring?Cloud?Gateway遠(yuǎn)程命令執(zhí)行漏洞分析(CVE-2022-22947)

    使用Spring Cloud Gateway的應(yīng)用程序在Actuator端點(diǎn)啟用、公開和不安全的情況下容易受到代碼注入的攻擊,攻擊者可以惡意創(chuàng)建允許在遠(yuǎn)程主機(jī)上執(zhí)行任意遠(yuǎn)程執(zhí)行的請求,這篇文章主要介紹了Spring?Cloud?Gateway遠(yuǎn)程命令執(zhí)行漏洞(CVE-2022-22947),需要的朋友可以參考下
    2023-03-03
  • 解決FontConfiguration.getVersion報(bào)空指針異常的問題

    解決FontConfiguration.getVersion報(bào)空指針異常的問題

    這篇文章主要介紹了解決FontConfiguration.getVersion報(bào)空指針異常的問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • Java學(xué)習(xí)之Lambda表達(dá)式的使用詳解

    Java學(xué)習(xí)之Lambda表達(dá)式的使用詳解

    Lambda表達(dá)式是Java SE 8中一個(gè)重要的新特性,允許通過表達(dá)式來代替功能接口。本文將通過一些簡單的示例和大家講講Lamda表達(dá)式的使用,感興趣的可以了解一下
    2022-12-12
  • java如何通過流讀取圖片做base64編碼

    java如何通過流讀取圖片做base64編碼

    這篇文章主要介紹了java如何通過流讀取圖片做base64編碼問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-11-11
  • 基于springboot和redis實(shí)現(xiàn)單點(diǎn)登錄

    基于springboot和redis實(shí)現(xiàn)單點(diǎn)登錄

    這篇文章主要為大家詳細(xì)介紹了基于springboot和redis實(shí)現(xiàn)單點(diǎn)登錄,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-06-06
  • SpringBoot集成Swagger2生成接口文檔的方法示例

    SpringBoot集成Swagger2生成接口文檔的方法示例

    我們提供Restful接口的時(shí)候,API文檔是尤為的重要,它承載著對接口的定義,描述等,本文主要介紹了SpringBoot集成Swagger2生成接口文檔的方法示例,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-12-12
  • 使用MybatisPlus自定義模版中能獲取到的信息

    使用MybatisPlus自定義模版中能獲取到的信息

    這篇文章主要介紹了使用MybatisPlus自定義模版中能獲取到的信息,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-05-05
  • SpringCloud Zuul自定義filter代碼實(shí)例

    SpringCloud Zuul自定義filter代碼實(shí)例

    這篇文章主要介紹了SpringCloud Zuul自定義filter代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-04-04
  • spring profile 多環(huán)境配置管理詳解

    spring profile 多環(huán)境配置管理詳解

    這篇文章主要介紹了 spring profile 多環(huán)境配置管理詳解的相關(guān)資料,需要的朋友可以參考下
    2017-01-01
  • Java之HashMap案例詳解

    Java之HashMap案例詳解

    這篇文章主要介紹了Java之HashMap案例詳解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08

最新評論