Java 8原生API也可以開發響應式代碼?

asphalt-automobile-automotive-1172105.jpg

前段時間工作上比較忙,這篇文章一直沒來得及寫,本文是閱讀《Java8實戰》的時候,瞭解到Java 8裏已經提供了一個異步非阻塞的接口(CompletableFuture),可以實現簡單的響應式編程的模式,因此用這篇文章做個梳理。我是帶着下面這幾個問題去學習CompletableFuture這個接口的,

  1. CompletableFuture是爲了解決什麼問題而設計的?
  2. 它的使用場景是什麼?開源軟件中有實戰使用案例嗎?
  3. CompletableFuture的常用API都有哪些?如何使用?
  4. CompletableFuture和RxJava有什麼不同?

這篇文章梳理下來,基本上可以回答前面四個問題,OK,我們進入正文。

基本概念

圖片摘自Dubbo官方博客RPC(遠程方法調用)的四種方式有:oneway、sync、future和callback,在dubbo或bolt這類通信框架中,默認使用的是sync模式(同步+阻塞),future和callback都屬於異步模式,不過future模式在get的時候會阻塞,callback模式則不需要等待結果,有結果後服務端會回調請求方。

異步調用這類模式,比較適合的場景是IO密集型場景,要執行很多遠程調用的任務,並且這些調用耗時可能比較久。以openwrite中的一個case爲例:我發佈一篇文章,需要給幾個不同的寫作平臺創建文章,這時候我不希望這個過程是順序的,就比較適合用異步調用模式。

Future模式除了在get()調用的時候會阻塞外,還有其他的侷限性,例如:沒有使用Java Lambda表達式的優勢,對一連串的異步調用可以支持,但是寫出來的代碼會比較複雜。

CompletableFuture的常用API

閱讀CompletableFuture的API的時候,我有一個體會——CompletableFuture之於Future,除了增加了回調這個最重要的特性,其他的特性有點像Stream對於集合迭代的增強。

使用CompletableFuture,我們可以像Stream一樣使用一部調用,可以處理一些級聯的異步調用(類似於Stream裏的flatMap)、可以過濾一些無用的異步調用(anyOf、allOf)。

下面這張圖是我按照自己的理解,梳理除了CompletableFuture常見的API,閱讀的時候需要注意下面幾個點:

  1. 把握幾個大的分類:創建CompletableFuture、獲取CompletableFuture的執行結果、主動結束CompletableFuture、異步調用任務的組合處理;
  2. 看着方法多,但是有規律可循,例如apply字樣的接口,傳入的方法參數都是有返回值的;
  3. 帶either字樣的,都是多個異步任務有一個滿足條件即可的;
  4. 帶executor方法的,都表示該方法可以用自定義的線程池來優化性能。

CompletableFuture的API

Dubbo項目中的使用案例

Dubbo對於異步化的支持起始在2.6.x中就有提供,是在發佈bean的時候加個屬性配置——async=true,然後利用上下文將異步標識一層層傳遞下去。在之前的公司中有一次排查dubbo(當時我們用的是dubbox)異步調用的問題,最後查到的原因就是多個異步調用,上下文裏的信息串了。

Dubbo 2.7 中使用了 JDK1.8 提供的 CompletableFuture 原生接口對自身的異步化做了改進。CompletableFuture 可以支持 future 和 callback 兩種調用方式。在Dubbo最新的master代碼中,我知道了Dubbo的異步結果的定義,它的類圖如下,可以看出AsyncRpcResult是一個CompletableFuture接口的實現。

AsyncRpcResult.png

實戰Demo

通過下面的例子,可以看出CompletableFuture的最大好處——callback特性。首先定義一個接口,其中包括同步接口和該接口的異步版本。

public interface AsyncInterfaceExample {

    String computeSomeThine();

    CompletableFuture<String> computeSomeThingAsync();
}

然後定義該接口的實現類,可以看出,如果要講現有的同步接口異步化,是比較容易的;

public class AsyncInterfaceExampleImpl implements AsyncInterfaceExample {

    @Override
    public String computeSomeThine() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return "hello, world";
    }

    @Override
    public CompletableFuture<String> computeSomeThingAsync() {
        return CompletableFuture.supplyAsync(this::computeSomeThine);
    }
}

然後看下我們的測試case,如下:

public class AsyncInterfaceExampleTest {

    private static String getOtherThing() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return "other";
    }

    public static void main(String[] args) {
        AsyncInterfaceExample asyncInterfaceExample = new AsyncInterfaceExampleImpl();

        //case1 同步調用
        long start = System.currentTimeMillis();
        String someThing = asyncInterfaceExample.computeSomeThine();
        String other = getOtherThing();
        System.out.println("cost:" + (System.currentTimeMillis() - start) + "  result:" + someThing + other);

        //case2 異步調用,使用回調
        start = System.currentTimeMillis();
        CompletableFuture<String> someThingFuture = asyncInterfaceExample.computeSomeThingAsync();
        other = getOtherThing();

        long finalStart = start;
        String finalOther = other;
        someThingFuture.whenComplete((returnValue, exception) -> {
            if (exception == null) {
                System.out.println(
                    "cost:" + (System.currentTimeMillis() - finalStart) + "  result:" + returnValue + finalOther);
            } else {
                exception.printStackTrace();
            }
        });
    }
}

上面這個案例的執行結果如下圖所示:執行結果***本號專注於後端技術、JVM問題排查和優化、Java面試題、個人成長和自我管理等主題,爲讀者提供一線開發者的工作和成長經驗,期待你能在這裏有所收穫。javaadu

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章