一文帶你徹底了解Java異步編程

隨著RxJava蒸殿、Reactor等異步框架的流行,異步編程受到了越來(lái)越多的關(guān)注,尤其是在IO密集型的業(yè)務(wù)場(chǎng)景中拼窥,相比傳統(tǒng)的同步開(kāi)發(fā)模式裆泳,異步編程的優(yōu)勢(shì)越來(lái)越明顯叹洲。

那到底什么是異步編程?異步化真正的好處又是什么工禾?如何選擇適合自己團(tuán)隊(duì)的異步技術(shù)运提?在實(shí)施異步框架落地的過(guò)程中有哪些需要注意的地方?

本文從以下幾個(gè)方面結(jié)合真實(shí)項(xiàng)目異步改造經(jīng)驗(yàn)對(duì)異步編程進(jìn)行分析闻葵,希望能給大家一些客觀認(rèn)識(shí):

  1. 使用RxJava異步改造后的效果
  2. 什么是異步編程民泵?異步實(shí)現(xiàn)原理
  3. 異步技術(shù)選型參考
  4. 異步化真正的好處是什么?
  5. 異步化落地的難點(diǎn)及解決方案
  6. 擴(kuò)展:異步其他解決方案-協(xié)程

使用RxJava異步改造后的效果

下圖是我們后端java項(xiàng)目使用RxJava改造成異步前后的RT(響應(yīng)時(shí)長(zhǎng))效果對(duì)比:


在這里插入圖片描述

在這里插入圖片描述

統(tǒng)計(jì)數(shù)據(jù)基于App端的gateway槽畔,以75線為準(zhǔn)栈妆,還有80、85厢钧、90鳞尔、99線,從圖中可以看出改成異步后接口整體的平均響應(yīng)時(shí)長(zhǎng)降低了 40% 左右早直。

(響應(yīng)時(shí)間是以發(fā)送請(qǐng)求到收到后端接口響應(yīng)數(shù)據(jù)的時(shí)長(zhǎng)寥假,上圖改造的這個(gè)后端java接口內(nèi)部流程比較復(fù)雜,因?yàn)楣径际俏⒎?wù)架構(gòu)莽鸿,該接口內(nèi)部又調(diào)用了6個(gè)其他服務(wù)的接口昧旨,最后把這些接口的數(shù)據(jù)匯總在一起返回給前端)

這張圖是同步接口和改造成異步接口前后的CPU負(fù)載情況對(duì)比

改造前cpu load : 35.46

在這里插入圖片描述

改造后cpu load : 14.25
在這里插入圖片描述

改成異步后CPU的負(fù)載情況也有明顯下降,但CPU使用率并無(wú)影響(一般情況下異步化后cpu的利用率會(huì)有所提高祥得,但要看具體的業(yè)務(wù)場(chǎng)景)

CPU LoadAverage:一段時(shí)間內(nèi)處于可運(yùn)行狀態(tài)和不可中斷狀態(tài)的進(jìn)程平均數(shù)量兔沃。(可運(yùn)行分為正在運(yùn)行進(jìn)程和正在等待CPU的進(jìn)程;不可中斷則是它正在做某些工作不能被中斷比如等待磁盤(pán)IO级及、網(wǎng)絡(luò)IO等)

而我們的服務(wù)業(yè)務(wù)場(chǎng)景大部分都是IO密集型業(yè)務(wù)乒疏,功能實(shí)現(xiàn)很多需要依賴底層接口,會(huì)進(jìn)行頻繁的IO操作饮焦。

下圖是2019年在全球架構(gòu)師峰會(huì)上阿里分享的異步化改造后的RT和QPS效果:


圖片來(lái)源:淘寶應(yīng)用架構(gòu)升級(jí)——反應(yīng)式架構(gòu)的探索與實(shí)踐

什么是異步編程怕吴?

響應(yīng)式編程 + NIO

1.異步和同步的區(qū)別:

我們先從I/O的角度看下同步模式下接口A調(diào)用接口B的交互流程:

下圖是傳統(tǒng)的同步模式下io線程的交互流程,可以看出io是阻塞的县踢,即bio的運(yùn)行模式

在這里插入圖片描述

接口A發(fā)起調(diào)用接口B后转绷,這段時(shí)間什么事情也不能做,主線程阻塞一直等到接口B數(shù)據(jù)返回硼啤,然后才能進(jìn)行其他操作议经,可想而知如果接口A調(diào)用的接口不止B的話(A->B->C->D->E。。煞肾。)咧织,那么等待的時(shí)間也是遞增的,而且這期間CPU也要一直占用著籍救,白白浪費(fèi)資源习绢,也就是上圖看到的 cpu load 高的原因。

而且還有一個(gè)隱患就是如果調(diào)用的其他服務(wù)中的接口比如C超時(shí)蝙昙,或接口C掛掉了闪萄,那么對(duì)調(diào)用方服務(wù)A來(lái)說(shuō),剩余的接口比如D耸黑、E都會(huì)無(wú)限等待下去桃煎。。大刊。

其實(shí)大部分情況下我們收到數(shù)據(jù)后內(nèi)部的處理邏輯耗時(shí)都很短,這個(gè)可以通過(guò)埋點(diǎn)執(zhí)行時(shí)間統(tǒng)計(jì)三椿,大部分時(shí)間都浪費(fèi)在了IO等待上缺菌。

下面這個(gè)視頻演示了同步模式下我們線上環(huán)境真實(shí)的接口調(diào)用情況,即接口調(diào)用的線程執(zhí)行和變化情況搜锰,(使用的工具是JDK自帶的jvisual來(lái)監(jiān)控線程變化情況)

這里先交代下大致背景:服務(wù)端api接口A內(nèi)部一共調(diào)用了6個(gè)其他服務(wù)的接口伴郁,大致交互是這樣的:

A接口(B -> C -> D -> E -> F -> G)返回聚合數(shù)據(jù)

背景:使用Jemter測(cè)試工具壓測(cè)100個(gè)線程并發(fā)請(qǐng)求接口,以觀察線程的運(yùn)行情況(可以全屏觀看):

視頻地址:
同步模式下io線程運(yùn)行情況

(綠色表示線程是"運(yùn)行中"狀態(tài)蛋叼,平均執(zhí)行時(shí)間大約8秒)

http-nio-8080-exec*開(kāi)頭的是tomcat線程池中的線程焊傅,即前端請(qǐng)求我們后端接口時(shí)要通過(guò)tomcat服務(wù)器接收和轉(zhuǎn)發(fā)的線程,因?yàn)槲覀兒蠖薬pi接口內(nèi)部又調(diào)用了其他服務(wù)的6個(gè)接口(B狈涮、C狐胎、D、E歌馍、F握巢、G),同步模式下需要等待上一個(gè)接口返回?cái)?shù)據(jù)才能繼續(xù)調(diào)用下一個(gè)接口松却,所以可以從視頻中看出暴浦,大部分的http線程耗時(shí)都在8秒以上(綠色線條代表線程是"運(yùn)行中"狀態(tài),平均執(zhí)行時(shí)間大約8秒晓锻,包括等待接口返回的時(shí)間和我們內(nèi)部邏輯處理的總時(shí)間歌焦,因?yàn)槭潜镜丨h(huán)境測(cè)試,受機(jī)器和網(wǎng)絡(luò)影響較大)

然后我們?cè)倏聪庐惒侥J降慕换チ鞒萄舛撸磏io方式:

在這里插入圖片描述

大致流程就是接口A發(fā)起調(diào)用接口B的請(qǐng)求后就立即返回独撇,而不用阻塞等待接口B響應(yīng),這樣的好處是http-nio-8080-exec*線程可以馬上得到復(fù)用,接著處理下一個(gè)前端請(qǐng)求的任務(wù)券勺,如果接口B處理完返回?cái)?shù)據(jù)后绪钥,會(huì)有一個(gè)回調(diào)線程池處理真正的響應(yīng),即這種模式下我們的業(yè)務(wù)流程是http線程只處理請(qǐng)求关炼,回調(diào)線程處理接口響應(yīng)程腹。

這個(gè)視頻演示了異步模式下接口A的線程執(zhí)行情況,同樣也是使用Jemter測(cè)試工具壓測(cè)100個(gè)線程并發(fā)請(qǐng)求接口儒拂,以觀察線程的運(yùn)行情況(可以全屏觀看):

視頻地址:
異步模式下io線程運(yùn)行情況

(綠色表示線程是"運(yùn)行中"狀態(tài)寸潦,平均執(zhí)行時(shí)間在1.8秒左右)

模擬的條件和同步模式一樣,同樣是100個(gè)線程并發(fā)請(qǐng)求接口社痛,但這次http-nio-8080-exec*開(kāi)頭的線程只處理請(qǐng)求任務(wù)见转,而不再等待全部的接口返回,所以http的線程運(yùn)行時(shí)間普遍都很短(大部分在1.8秒左右完成)蒜哀,AsfThread-executor-*是我們系統(tǒng)封裝的回調(diào)線程池斩箫,處理底層接口的真正響應(yīng)數(shù)據(jù)。

演示視頻中的AsfThread-executor-*的回調(diào)線程只創(chuàng)建了30多個(gè)撵儿,而請(qǐng)求的http線程有100個(gè)乘客,也就是說(shuō)這30多個(gè)回調(diào)線程處理了接口B的100次響應(yīng)(其實(shí)應(yīng)該是600次,因?yàn)榻涌贐內(nèi)部又調(diào)用了6個(gè)其他接口淀歇,這6次也都是在異步線程里處理響應(yīng)的)易核,因?yàn)槊總€(gè)接口返回的時(shí)間不一樣,加上網(wǎng)絡(luò)傳輸?shù)臅r(shí)間浪默,所以可以利用這個(gè)時(shí)間差充分復(fù)用線程即cpu資源牡直,視頻中回調(diào)線程AsfThread-executor-*的綠色運(yùn)行狀態(tài)是多段的,表示復(fù)用了多次纳决,也就是少量回調(diào)線程處理了全部(600次)的響應(yīng)碰逸,這正是IO多路復(fù)用的機(jī)制。

nio模式下雖然http-nio-8080-exec*線程和回調(diào)線程AsfThread-executor-*的運(yùn)行時(shí)間都很短岳链,但是從http線程開(kāi)始到asf回調(diào)處理完返回給前端結(jié)果的時(shí)間和bio即同步模式下的時(shí)間差異不大(在相同的邏輯流程下)花竞,并不是nio模式下服務(wù)響應(yīng)的整體時(shí)間就會(huì)縮短,而是會(huì)提升CPU的利用率掸哑,因?yàn)镃PU不再會(huì)阻塞等待(不可中斷狀態(tài)減少)约急,這樣CPU就能有更多的資源來(lái)處理其他的請(qǐng)求任務(wù),相同單位時(shí)間內(nèi)能處理更多的任務(wù)苗分,所以nio模式帶來(lái)的好處是:

  • 提升QPS(用更少的線程資源實(shí)現(xiàn)更高的并發(fā)能力)
  • 降低CPU負(fù)荷,提高利用率

2.nio原理

在這里插入圖片描述

結(jié)合上面的接口交互圖可知厌蔽,接口B通過(guò)網(wǎng)絡(luò)返回?cái)?shù)據(jù)給調(diào)用方(接口A)這一過(guò)程,對(duì)應(yīng)底層實(shí)現(xiàn)就是網(wǎng)卡接收到返回?cái)?shù)據(jù)后摔癣,通過(guò)自身的DMA(直接內(nèi)存訪問(wèn))將數(shù)據(jù)拷貝到內(nèi)核緩沖區(qū)奴饮,這一步不需要CPU參與操作纬向,也就是把原先CPU等待的事情交給了底層網(wǎng)卡去處理,這樣CPU就可以專注于我們的應(yīng)用程序即接口內(nèi)部的邏輯運(yùn)算戴卜。

3.nio In Java

在這里插入圖片描述

nio在java里的實(shí)現(xiàn)主要是上圖中的幾個(gè)核心組件:channel逾条、bufferselector投剥,這些組件組合起來(lái)即實(shí)現(xiàn)了上面所講的多路復(fù)用機(jī)制师脂,如下圖所示:
在這里插入圖片描述

響應(yīng)式編程

1.什么是響應(yīng)式編程?它和傳統(tǒng)的編程方式有什么區(qū)別江锨?

響應(yīng)式可以簡(jiǎn)單的理解為收到某個(gè)事件或通知后采取的一系列動(dòng)作吃警,如上文中所說(shuō)的響應(yīng)操作系統(tǒng)的網(wǎng)絡(luò)數(shù)據(jù)通知,然后以回調(diào)的方式處理數(shù)據(jù)啄育。

傳統(tǒng)的命令式編程主要由:順序酌心、分支、循環(huán) 等控制流來(lái)完成不同的行為

響應(yīng)式編程的特點(diǎn)是:

  • 以邏輯為中心轉(zhuǎn)換為以數(shù)據(jù)為中心
  • 從命令式到聲明式的轉(zhuǎn)換

2.Java.Util.Concurrent.Future

在Java使用nio后無(wú)法立即拿到真實(shí)的數(shù)據(jù)挑豌,而且先得到一個(gè)"future"安券,可以理解為郵戳或快遞單,為了獲悉真正的數(shù)據(jù)我們需要不停的通過(guò)快遞單號(hào)查詢快遞進(jìn)度氓英,所以 J.U.C 中的 Future 是Java對(duì)異步編程的第一個(gè)解決方案完疫,通常和線程池結(jié)合使用,偽代碼形式如下:

ExecutorService executor = Executors.newCachedThreadPool(); // 線程池
Future<String> future = executor.submit(() ->{
    Thread.sleep(200); // 模擬接口調(diào)用债蓝,耗時(shí)200ms
    return "hello world";
});
// 在輸出下面異步結(jié)果時(shí)主線程可以不阻塞的做其他事情
// TODO 其他業(yè)務(wù)邏輯

System.out.println("異步結(jié)果:"+future.get()); //主線程獲取異步結(jié)果

Future的缺點(diǎn)很明顯:

  • 無(wú)法方便得知任務(wù)何時(shí)完成
  • 無(wú)法方便獲得任務(wù)結(jié)果
  • 在主線程獲得任務(wù)結(jié)果會(huì)導(dǎo)致主線程阻塞

3.ListenableFuture

Google并發(fā)包下的listenableFuture對(duì)Java原生的future做了擴(kuò)展,顧名思義就是使用監(jiān)聽(tīng)器模式實(shí)現(xiàn)的回調(diào)機(jī)制盛龄,所以叫可監(jiān)聽(tīng)的future饰迹。

Futures.addCallback(listenableFuture, new FutureCallback<String>() {
    @Override
    public void onSuccess(String result) {
        System.out.println("異步結(jié)果:" + result);
    }

    @Override
    public void onFailure(Throwable t) {
        t.printStackTrace();
    }
}, executor);

回調(diào)機(jī)制的最大問(wèn)題是:Callback Hell(回調(diào)地獄)

試想如果調(diào)用的接口多了,而且接口之間有依賴的話余舶,最終寫(xiě)出來(lái)的代碼可能就是下面這個(gè)樣子:


在這里插入圖片描述
  • 代碼的字面形式和其所表達(dá)的業(yè)務(wù)含義不匹配
  • 業(yè)務(wù)的先后關(guān)系在代碼層面變成了包含和被包含的關(guān)系
  • 大量使用 Callback 機(jī)制啊鸭,使應(yīng)該是先后的業(yè)務(wù)邏輯在代碼形式上表現(xiàn)為層層嵌套,這會(huì)導(dǎo)致代碼難以理解和維護(hù)

那么如何解決 Callback Hell 問(wèn)題呢?

響應(yīng)式編程

主要是以下兩種解決方式:

  • 事件驅(qū)動(dòng)機(jī)制
  • 鏈?zhǔn)秸{(diào)用(Lambda)

4.CompletableFuture

Java8里的CompletableFuture和Java9的Flow Api勉強(qiáng)算是上面問(wèn)題的解決方案:

CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() ->
    "hello"
);
// f2依賴f1的結(jié)果做轉(zhuǎn)換
CompletableFuture<String> f2 = f1.thenApplyAsync(t ->
    t + " world"
);
System.out.println("異步結(jié)果:" + f2.get());

CompletableFuture處理簡(jiǎn)單的任務(wù)可以使用匿值,但并不是一個(gè)完整的反應(yīng)式編程解決方案赠制,在服務(wù)調(diào)用復(fù)雜的情況下,存在服務(wù)編排挟憔、上下文傳遞钟些、柔性限流(背壓)方面的不足

如果使用CompletableFuture面對(duì)這些問(wèn)題可能需要自己額外造一些輪子,Java9的Flow雖然是基于Reactive Streams 規(guī)范實(shí)現(xiàn)的绊谭,但沒(méi)有RxJava政恍、Project Reactor這些異步框架豐富和強(qiáng)大和完整的解決方案。

當(dāng)然如果接口邏輯比較簡(jiǎn)單达传,完全可以使用listenableFutureCompletableFuture篙耗,關(guān)于他們的詳細(xì)用法可參考之前的一篇文章:Java異步編程指南

5.Reactive Streams

在網(wǎng)飛推出RxJava1.0并在Android端普及流行開(kāi)后迫筑,響應(yīng)式編程的規(guī)范也呼之欲出:

https://www.reactive-streams.org/

包括后來(lái)的RxJava2.0、Project Reactor都是基于Reactive Streams規(guī)范實(shí)現(xiàn)的宗弯。

關(guān)于他們和listenableFuture脯燃、 CompletableFuture的區(qū)別通過(guò)下面的例子大家應(yīng)該就會(huì)清楚。

比如下面的基于回調(diào)的代碼示例:獲取用戶的5個(gè)收藏列表功能


在這里插入圖片描述

圖中標(biāo)注序號(hào)的步驟對(duì)應(yīng)如下:

  1. 根據(jù)uid調(diào)用用戶收藏列表接口userService.getFavorites
  2. 成功的回調(diào)邏輯
  3. 如果用戶收藏列表為空
  4. 調(diào)用推薦服務(wù)suggestionService.getSuggestions
  5. 推薦服務(wù)成功后的回調(diào)邏輯
  6. 取前5條推薦并展示(Java8 Stream api)
  7. 推薦服務(wù)失敗的回調(diào),展示錯(cuò)誤信息
  8. 如果用戶收藏列表有數(shù)據(jù)返回
  9. 取前5條循環(huán)調(diào)用詳情接口favoriteService.getDetails 成功回調(diào)則展示詳情,失敗回調(diào)則展示錯(cuò)誤信息

可以看出主要邏輯都是在回調(diào)函數(shù)(onSuccess()蒙保、onError())中處理的辕棚,在可讀性和后期維護(hù)成本上比較大。

基于Reactive Streams規(guī)范實(shí)現(xiàn)的響應(yīng)式編程解決方案如下:


在這里插入圖片描述
  1. 調(diào)用用戶收藏列表接口
  2. 壓平數(shù)據(jù)流調(diào)用詳情接口
  3. 如果收藏列表為空調(diào)用推薦接口
  4. 取前5條
  5. 切換成異步線程處理上述聲明接口返回結(jié)果)
  6. 成功則展示正常數(shù)據(jù),錯(cuò)誤展示錯(cuò)誤信息

可以看出因?yàn)檫@些異步框架提供了豐富的api追他,所以我們可以把主要精力放在數(shù)據(jù)的流轉(zhuǎn)上坟募,而不是原來(lái)的邏輯控制上。這也是異步編程帶來(lái)的思想上的轉(zhuǎn)變邑狸。

下圖是RxJava的operator api

如果這些操作符滿足不了你的需求懈糯,你也可以自定義操作符

所以說(shuō)異步最吸引人的地方在于資源的充分利用,不把資源浪費(fèi)在等待的時(shí)間上(nio)单雾,代價(jià)是增加了程序的復(fù)雜度赚哗,而Reactive Program封裝了這些復(fù)雜性,使其變得簡(jiǎn)單硅堆。

因此我們無(wú)論使用哪種異步框架屿储,盡量使用框架提供的api,而不是像上圖那種基于回調(diào)業(yè)務(wù)的代碼渐逃,把業(yè)務(wù)邏輯都寫(xiě)在onSuccess够掠、onError等回調(diào)方法里,這樣無(wú)法發(fā)揮異步框架的真正作用:

Codes Like Sync茄菊,Works Like Async

即以同步的方式編碼疯潭,達(dá)到異步的效果與性能,兼顧可維護(hù)性與可伸縮性

異步框架技術(shù)選型

圖片來(lái)源:淘寶應(yīng)用架構(gòu)升級(jí)——反應(yīng)式架構(gòu)的探索與實(shí)踐

上面這張圖也是阿里在2019年的深圳全球架構(gòu)師峰會(huì)上分享的PPT截圖(文章末尾有鏈接)面殖,供大家參考竖哩,選型標(biāo)準(zhǔn)主要是基于穩(wěn)定性、普及性脊僚、成本這3點(diǎn)考慮

如果是我個(gè)人更愿意選擇Project Reactor作為首選異步框架相叁,(具體差異網(wǎng)上很多分析,大家可以自行百度谷歌)辽幌,還有一點(diǎn)是因?yàn)镹etflix的尿性增淹,推出的開(kāi)源產(chǎn)品漸漸都不維護(hù)了,而且Project Reactor提供了reactor-adapter組件舶衬,可以方便的和RxJava的api轉(zhuǎn)換埠通。

其實(shí)還有Vert.x也算異步框架 (底層使用netty實(shí)現(xiàn)nio, 最新版已支持reactive stream規(guī)范)

異步化真正的好處

Scalability

伸縮性主要體現(xiàn)在以下兩個(gè)方面:

  • elastic 彈性
  • resilient 容錯(cuò)性

(異步化在平時(shí)不會(huì)明顯降低 RT、提高 QPS逛犹,文章開(kāi)頭的數(shù)據(jù)也是在大促這種流量高峰下的體現(xiàn)出的異步效果)

從架構(gòu)和應(yīng)用等更高緯度看待異步帶來(lái)的好處則會(huì)提升系統(tǒng)的兩大能力:彈性容錯(cuò)性

前者反映了系統(tǒng)應(yīng)對(duì)壓力的表現(xiàn)端辱,后者反映了系統(tǒng)應(yīng)對(duì)故障的表現(xiàn)

1.容錯(cuò)性

像RxJava梁剔,Reactor這些異步框架處理回調(diào)數(shù)據(jù)時(shí)一般會(huì)切換線程上下文,其實(shí)就是使用不同的線程池來(lái)隔離不同的數(shù)據(jù)流處理邏輯舞蔽,下圖說(shuō)明了這一特性的好處:

在這里插入圖片描述

即利用異步框架支持線程池切換的特性實(shí)現(xiàn)服務(wù)/接口隔離荣病,進(jìn)而提高系統(tǒng)的高可用

2.彈性

在這里插入圖片描述

back-pressure是一種重要的反饋機(jī)制渗柿,相比于傳統(tǒng)的熔斷限流等方式个盆,是一種更加柔性的自適應(yīng)限流。使得系統(tǒng)得以優(yōu)雅地響應(yīng)負(fù)載朵栖,而不是在負(fù)載下崩潰颊亮。

異步化落地的難點(diǎn)及解決方案

還是先看下淘寶總結(jié)的異步改造中難點(diǎn)問(wèn)題:


圖片來(lái)源:淘寶應(yīng)用架構(gòu)升級(jí)——反應(yīng)式架構(gòu)的探索與實(shí)踐

中間件全異步牽涉到到公司中臺(tái)化戰(zhàn)略或框架部門(mén)的支持,包括公司內(nèi)部常用的中間件比如MQ陨溅、redis终惑、dal等,超出了本文討論的范圍门扇,感興趣的可以看下文章末尾的參考資料雹有。

線程模型統(tǒng)一的背景在上一節(jié)異步化好處時(shí)有提到過(guò),其實(shí)主要還是對(duì)線程池的管理臼寄,做好服務(wù)隔離霸奕,線程池設(shè)置和注意事項(xiàng)可以參考之前的兩篇文章:Java踩坑記系列之線程池線程池ForkJoinPool簡(jiǎn)介

這里主要說(shuō)下上下文傳遞和阻塞檢測(cè)的問(wèn)題:

1.上下文傳遞

改造成異步服務(wù)后吉拳,不能再使用ThreadLocal傳遞上下文context质帅,因?yàn)楫惒娇蚣鼙热鏡xJava一般在收到通知后會(huì)先調(diào)用observeOn()方法切換成另外一個(gè)線程處理回調(diào),比如我們?cè)谡?qǐng)求接口時(shí)在ThreadLocal的context里設(shè)置了一個(gè)值留攒,在回調(diào)線程里從context里取不到這個(gè)值的临梗,因?yàn)榇藭r(shí)已經(jīng)不是同一個(gè)ThreadLocal了,所以需要我們手動(dòng)在切換上下文的時(shí)候傳遞context從一個(gè)線程到另一個(gè)線程環(huán)境稼跳,偽代碼如下:

Context context = ThreadLocalUtils.get(); // 獲取當(dāng)前線程的上下文
single.observeOn(scheduler).doOnEvent((data, error) -> ThreadLocalUtils.set(context)); // 切換線程后在doOnEvent里重新給新的線程賦值context

observeOn()方法切換成另外一個(gè)線程后調(diào)用doOnEvent方法將原來(lái)的context賦給新的線程ThreadLocal

注意:這里的代碼只是提供一種解決思路,實(shí)際在使用前和使用后還要考慮清空ThreadLocal吃沪,因?yàn)榫€程有可能會(huì)回收到線程池下次復(fù)用汤善,而不是立即清理,這樣就會(huì)污染上下文環(huán)境票彪。

可以將傳遞上下文的方法封裝成公共方法红淡,不需要每次都手動(dòng)切換。

2.阻塞檢測(cè)

阻塞檢測(cè)主要是要能及時(shí)發(fā)現(xiàn)我們某個(gè)異步任務(wù)長(zhǎng)時(shí)間阻塞的發(fā)生降铸,比如異步線程執(zhí)行時(shí)間過(guò)長(zhǎng)進(jìn)而影響整個(gè)接口的響應(yīng)在旱,原來(lái)同步場(chǎng)景下我們的日志都是串行記錄到ES或Cat上的,現(xiàn)在改成異步后推掸,每次處理接口數(shù)據(jù)的邏輯可能在不同的線程中完成桶蝎,這樣記錄的日志就需要我們主動(dòng)去合并(依據(jù)具體的業(yè)務(wù)場(chǎng)景而定)驻仅,如果日志無(wú)法關(guān)聯(lián)起來(lái),對(duì)我們排查問(wèn)題會(huì)增加很多難度登渣。所幸的是隨著異步的流行噪服,現(xiàn)在很多日志和監(jiān)控系統(tǒng)都已支持異步了。

Project Reactor 自己也有阻塞檢測(cè)功能胜茧,可以參考這篇文章:BlockHound

3.其他問(wèn)題

除了上面提到的兩個(gè)問(wèn)題外粘优,還有一些比如RxJava2.0之后不支持返回null,如果我們?cè)瓉?lái)的代碼或編程習(xí)慣所致返回結(jié)果有null的情況呻顽,可以考慮使用java8的Optional.ofNullable()包裝一下雹顺,然后返回的RxJava類型是這樣的:Single<Optional>,其他異步框架如果有類似的問(wèn)題同理廊遍。

異步其他解決方案:纖程/協(xié)程

  • Quasar
  • Kilim
  • Kotlin
  • Open JDK Loom
  • AJDK wisp2

協(xié)程并不是什么新技術(shù)嬉愧,它在很多語(yǔ)言中都有實(shí)現(xiàn),比如 Python昧碉、Lua英染、Go 都支持協(xié)程。

協(xié)程與線程不同之處在于被饿,線程由內(nèi)核調(diào)度四康,而協(xié)程的調(diào)度是進(jìn)程自身完成的。這樣就可以不受操作系統(tǒng)對(duì)線程數(shù)量的限制狭握,一個(gè)線程內(nèi)部可以創(chuàng)建成千上萬(wàn)個(gè)協(xié)程闪金。因?yàn)樯衔闹v到的異步技術(shù)都是基于線程的操作和封裝,Java中的線程概念對(duì)應(yīng)的就是操作系統(tǒng)的線程论颅。

1.Quasar哎垦、Kilim

開(kāi)源的Java輕量級(jí)線程(協(xié)程)框架,通過(guò)利用Java instrument技術(shù)對(duì)字節(jié)碼進(jìn)行修改恃疯,使方法掛起前后可以保存和恢復(fù)JVM棧幀漏设,方法內(nèi)部已執(zhí)行到的字節(jié)碼位置也通過(guò)增加狀態(tài)機(jī)的方式記錄,在下次恢復(fù)執(zhí)行可直接跳轉(zhuǎn)至最新位置今妄。

2.Kotlin

Kotlin Coroutine 協(xié)程庫(kù)郑口,因?yàn)?Kotlin 的運(yùn)行依賴于 JVM,不能對(duì) JVM 進(jìn)行修改盾鳞,因此Kotlin不能在底層支持協(xié)程犬性。同時(shí)Kotlin 是一門(mén)編程語(yǔ)言,需要在語(yǔ)言層面支持協(xié)程腾仅,所以Kotlin 對(duì)協(xié)程支持最核心的部分是在編譯器中完成乒裆,這一點(diǎn)其實(shí)和Quasar、Kilim實(shí)現(xiàn)原理類似推励,都是在編譯期通過(guò)修改字節(jié)碼的方式實(shí)現(xiàn)協(xié)程

3.Project Loom

Project Loom 發(fā)起的原因是因?yàn)殚L(zhǎng)期以來(lái)Java 的線程是與操作系統(tǒng)的線程一一對(duì)應(yīng)的鹤耍,這限制了 Java 平臺(tái)并發(fā)能力提升肉迫,Project Loom 是從 JVM 層面對(duì)多線程技術(shù)進(jìn)行徹底的改變

OpenJDK 在2018年創(chuàng)建了 Loom 項(xiàng)目惰蜜,目標(biāo)是在JVM上實(shí)現(xiàn)輕量級(jí)的線程昂拂,并解除JVM線程與內(nèi)核線程的映射。其實(shí) Loom 項(xiàng)目的核心開(kāi)發(fā)人員正是從Quasar項(xiàng)目過(guò)來(lái)的抛猖,目的也很明確格侯,就是要將這項(xiàng)技術(shù)集成到底層JVM里,所以Quasar項(xiàng)目目前已經(jīng)不維護(hù)了财著。联四。。

4.AJDK Wisp2

Alibaba Dragonwell 是阿里巴巴的 Open JDK 發(fā)行版撑教,提供長(zhǎng)期支持朝墩。dragonwell8已開(kāi)源協(xié)程功能(之前的版本是不支持的),開(kāi)啟jvm命令:-XX:+UseWisp2 即支持協(xié)程伟姐。

總結(jié)

  • Future 在異步方面支持有限
  • Callback 在編排能力方面有 Callback Hell 的短板
  • Project Loom 最新支持的Open JDK版本是16收苏,目前還在測(cè)試中
  • AJDK wisp2 需要換掉整個(gè)JVM,需要考慮改動(dòng)成本和收益比

所以目前實(shí)現(xiàn)異步化比較成熟的方案是 Reactive Streams

以上是老K對(duì)異步編程的理解愤兵,如有問(wèn)題歡迎指正鹿霸。

另外開(kāi)發(fā)人員也不要將自己局限在某種特定技術(shù)上,對(duì)各種技術(shù)都保持開(kāi)放的態(tài)度是開(kāi)發(fā)人員技能不斷提高的前提秆乳。只會(huì)簡(jiǎn)單說(shuō)某某語(yǔ)言懦鼠、某某技術(shù)比其它技術(shù)更好的人永遠(yuǎn)不會(huì)成為出色的產(chǎn)品:dog:。

From Reactive, More Than Reactive

文章來(lái)源:http://javakk.com/563.html
也歡迎大家關(guān)注我的公眾號(hào)【Java老K】獲取更多干貨

參考資料

全球架構(gòu)師峰會(huì)(第二天-淘寶應(yīng)用架構(gòu)升級(jí)——反應(yīng)式架構(gòu)的探索與實(shí)踐)
https://archsummit.infoq.cn/2019/shenzhen/schedule

Project Reactor:
https://projectreactor.io/

RxJava:
http://reactivex.io/

openjdk loom
jdk:http://jdk.java.net/loom/
wiki:https://wiki.openjdk.java.net/display/loom/Main
github:https://github.com/openjdk/loom

阿里 jdk:
https://github.com/alibaba/dragonwell8

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末屹堰,一起剝皮案震驚了整個(gè)濱河市肛冶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌扯键,老刑警劉巖睦袖,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異荣刑,居然都是意外死亡扣泊,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)嘶摊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人评矩,你說(shuō)我怎么就攤上這事叶堆。” “怎么了斥杜?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵虱颗,是天一觀的道長(zhǎng)沥匈。 經(jīng)常有香客問(wèn)我,道長(zhǎng)忘渔,這世上最難降的妖魔是什么高帖? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮畦粮,結(jié)果婚禮上散址,老公的妹妹穿的比我還像新娘。我一直安慰自己宣赔,他們只是感情好预麸,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著儒将,像睡著了一般吏祸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上钩蚊,一...
    開(kāi)封第一講書(shū)人閱讀 52,475評(píng)論 1 312
  • 那天贡翘,我揣著相機(jī)與錄音,去河邊找鬼砰逻。 笑死鸣驱,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的诱渤。 我是一名探鬼主播丐巫,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼勺美!你這毒婦竟也來(lái)了递胧?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤赡茸,失蹤者是張志新(化名)和其女友劉穎缎脾,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體占卧,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡遗菠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了华蜒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辙纬。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖叭喜,靈堂內(nèi)的尸體忽然破棺而出贺拣,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布譬涡,位于F島的核電站闪幽,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏涡匀。R本人自食惡果不足惜盯腌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望陨瘩。 院中可真熱鬧腕够,春花似錦、人聲如沸拾酝。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蒿囤。三九已至客们,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間材诽,已是汗流浹背底挫。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留脸侥,地道東北人建邓。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像睁枕,于是被迫代替她去往敵國(guó)和親官边。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361