java8 CompletableFuture Stupid As That

? java8 CompletableFutureScala Future 相比,充斥了沒所謂沒必要的概念和名詞,不過因?yàn)樾枰疾霩Base2.2的異步客戶端,所以需要了解基本用法,首先了解下CompletableFuture,之后記錄一下HBase自帶異步客戶端的測試情況。

IBM Java并發(fā)性:阻塞還是不阻塞?

A Brief Overview Of Scala Futures

掘金:Java 和 Scala 中的 Future

直到j(luò)ava7:The only way to get the result from a future object is to call its get() method in the thread that submitted the task. This method is blocking;所以從java5推出一直到7的 Future 只能相當(dāng)于個(gè)概念車吧,java8開始量產(chǎn)車

? java8的java.util.concurrent包低調(diào)加推:CompletionStage接口及其CompletableFuture 實(shí)現(xiàn)類,使得java Future真正具備實(shí)用性,一定程度上使得java向面向并發(fā)語言前進(jìn)了一大步。

? java8圍繞CompletableFuture 構(gòu)建了CompletionStage patterns.? CompletionStage顧名思義是一串操作的各個(gè)階段,CompletionStage就是在這些異步階段之間傳遞參數(shù)和執(zhí)行Task的模型,java8是以類似Akka Stream的形式構(gòu)建異步函數(shù)執(zhí)行Chain:

? 1、CompletableFuture<T> cf1 =??CompletableFuture.supplyAsync(()?->?{?? ()?-> 生產(chǎn)操作; },?executor) //supplyAsync是靜態(tài)方法,方便你直接開始一段異步調(diào)用鏈

? 2、CompletableFuture<T> cf2 =??cf1.thenApply( (argument) -> 中間處理并返回結(jié)果 )

? 3、CompletableFuture<T> cf3 =??cf2.thenAccept( (argument) -> 收尾處理 )

? 4、還可以再加最后一步:cf3.thenRun( ()?-> 額外操作 )

? 下游的CompletionStages由上游CompletionStages的完成事件自動觸發(fā)執(zhí)行,不需要你的main線程介入,上游返回的結(jié)果會fed 給下游,除了第一個(gè)supplyAsync方法接收參數(shù)類型為Supplier,上述3個(gè)CompletionStages參數(shù)的Task類型分別是Function、Consumer和Runnable.? 這些類型由方法名暗示:方法名前綴是then的,其后綴暗示了Task類型(run for Runnable, accept for Consumer, and apply for Function)

? 因?yàn)閖ava8只有殘缺的函數(shù)式特性和蹩腳的泛型,所以不像scala的Task就是一段表達(dá)式這么統(tǒng)一精煉,為了適配有無輸入?yún)?shù)和返回值,不得不定義出Runnable、Consumer、Supplier和Function等幾種Task類型,這些所謂的“函數(shù)式接口”注定是過渡性名詞,到j(luò)ava14這些名詞都會消弭無形。

? Consumer、Supplier本質(zhì)都是函數(shù)而已,你可以借助Akka Stream的概念理解為:

? 1、Supplier無參數(shù)只有返回,相當(dāng)于無參函數(shù),它只生產(chǎn)數(shù)據(jù)用于向后傳遞,類似Source;

? 2、Consumer只有參數(shù)無返回,它接收參數(shù)數(shù)據(jù)(一般是Supplier生產(chǎn)的)做處理,無返回,類似Sink.

? 3、Function有參數(shù)有返回。既然有上述二者,當(dāng)然也會有位于中間、接收參數(shù)、做處理并返回的東西,即java.util.function.Function,類似Flow.?

? 就是這么隱晦猥褻,就像就為了個(gè)返回值還要在Runnable之外再定義一個(gè)Callable,就為了有沒有接收參數(shù)有沒有返回值,他們竟然能總結(jié)出四種情況的“函數(shù)式接口”:

茴字的四種寫法

? 令人懷疑java8就為了匹配java.util.concurrent并發(fā)包,硬擼出來個(gè)java.util.function包,所以如果你要用java.util.concurrent寫高并發(fā)程序、還必須用到j(luò)ava.util.function的一堆東西... 這就是面向?qū)ο箅y以為繼的表現(xiàn):名詞泛濫,最終可能會達(dá)到一個(gè)無法收拾的地步,就像某種沒有構(gòu)詞法的語言,每當(dāng)有新事物出現(xiàn),就得發(fā)明一個(gè)新名詞指代它,這種龜毛語言能存在幾年?除了不斷發(fā)明新名詞,連帶著類內(nèi)部的方法也不得不膨脹,他們自己也承認(rèn):

That makes a lot of methods in the CompletableFuture class!? Three types of tasks, times four types of chaining and composition, times three ways of specifying which ExecutorService we want this task to be run. We have 36 methods to chain tasks. This is probably one of the elements that make this class complex: the high number of available methods.

? java8開發(fā)者自己也覺得長此以往名詞王國國將不國,終于開始支持()?->"Hello?world"這種字面函數(shù)語法了,java.util.function包里的一坨東西,你可以眼不見心不煩了,上述的幾個(gè)新名詞你可以不管它們,在編碼使用的時(shí)候:

? 1、使用Supplier一般寫作:supplyAsync( ()?->"Hello?world" )

? 2、使用Consumer寫作:thenAccept*( s -> println(s) ) //*表示Async //相當(dāng)于to方法

? 3、使用Function寫作:thenApply*( s -> println(s); s+1 ) //*表示Async //相當(dāng)于via方法

? 4、then開頭的方法命名方式是:run for Runnable, accept for Consumer, and apply for Function,所以除了上述thenAccept、thenApply還有一個(gè):

? thenRun*( ()?->Unit ) ? //*表示Async;()?->Unit表示Runnable,無參數(shù)無返回的無聊行為

? 避免了繼續(xù)寫這種裹腳布一樣的代碼:

new Consumer[String]() {

? ? ? def accept(s: String): Unit = {

? ? ? ? System.out.println(s)

? ? ? }

}

? 那么thenAccept和thenAcceptAsync、thenApply和thenApplyAsync、thenRun和thenRunAsync都有啥區(qū)別呢?簡單說是:An async method executes its task in the default fork/join pool, unless it takes an Executor, in which case, the task will be executed in this Executor.? 即:帶async后綴的方法,如果傳入一個(gè)新的Executor則提交該Executor運(yùn)行,如果沒有傳入則會運(yùn)行在一個(gè)通用fork/join線程池。java源碼的注釋更詳細(xì):

? Actions supplied for dependent completions of non-async methods may be performed by the thread that completes the current CompletableFuture, or by any other caller of a completion method.

? All? async? methods without an explicit Executor argument are performed using the {@link ForkJoinPool#commonPool()} (unless it does not support a parallelism level of at least two, in which case, a new Thread is created to run each task).? To simplify monitoring, debugging, and tracking, all generated asynchronous tasks are instances of the marker interface {@link AsynchronousCompletionTask}

? OK,我們要盡量異步化執(zhí)行所有任務(wù)的話,理論上應(yīng)該盡量使用帶Async后綴的方法,同時(shí)搭配規(guī)劃線程池的使用,了解到這一層足矣了。不過通過HBase異步客戶端的初步測試,帶Async后綴的方法雖然單獨(dú)運(yùn)行在一個(gè)ForkJoinPool.commonPool,但是完成任務(wù)的時(shí)間還不如不帶Async后綴的方法(注意這并不能說明性能不好),我的Source是HBase異步客戶端AsyncTable異步表的putAll方法,它生產(chǎn)出來一個(gè)CompletableFuture<Void>,因?yàn)楹罄m(xù)任務(wù)比較簡單直接是thenAccept完事,這些不帶Async后綴的方法將復(fù)用前一個(gè)方法線程池:HBase異步客戶端的線程池Default-IPC-NioEventLoopGroup

? 實(shí)際上這些性能都無所謂,單機(jī)硬件的并發(fā)度是有上限的,我們使用異步客戶端也不能提升HBase真正落盤入庫的效率,我們使用的目的主要是不要阻塞程序中的其他線程罷了,其次是將任務(wù)分階段、這些階段全異步執(zhí)行之后,在大并發(fā)場景會帶來總體的吞吐量效率提升,這一塊提升肉眼可見,不過肯定不是特別大幅度的提升,所謂異步仍然是空間換時(shí)間,雖然異步方法都是無阻塞返回了,但是不代表數(shù)據(jù)在HBase真的這么快落盤了,只是待提交的數(shù)據(jù)仍然在內(nèi)存中由某個(gè)線程代持。我的測試是對HBase表做putAll(不寫WAL)、然后flush,過程中發(fā)現(xiàn)HBase表的putAll操作耗時(shí)是最突出的,所以只要將這一步異步化就能收到關(guān)鍵效果了,這一步是調(diào)用HBase異步表API,從這一步開始已經(jīng)是異步化了,至于這些異步線程的真實(shí)效率并不是我們主要關(guān)心的,我們主要關(guān)心我們的main線程不會被IO阻塞,實(shí)際上對我的場景來說,HBase異步客戶端的作用無足輕重,可用可不用,只是異步的AsyncAdmin和 AsyncTable線程安全不需要關(guān)閉、代碼少點(diǎn)麻煩而已。

? 更多的用法細(xì)節(jié)可以看java8 CompletableFuture,Scala Future另一個(gè)很好的參考文檔是Akka Futures。總之,學(xué)院派的OO已經(jīng)走到了盡頭,異步編程的復(fù)雜性本來就是指數(shù)級增長,java8的異步編程基礎(chǔ)已經(jīng)這么繁雜冗長,用它去做大規(guī)模并發(fā)系統(tǒng)開發(fā)可以肯定幾乎是不可能的事,對后續(xù)java版本也不會在這種薄弱的基礎(chǔ)上繼續(xù),事實(shí)上,java14的語法已經(jīng)全面靠近scala.(估計(jì)在java14,java.util.function包已經(jīng)被廢棄),不過回過頭來看Scala的實(shí)現(xiàn),本質(zhì)也是一樣的,比如function22,其實(shí)在JVM之上做法都類似,都只能做到讓你眼不見心不煩。

? 我的場景下,實(shí)際上用了三個(gè)不同類型的線程池:

? 1、SI的task:executor:org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor,只是基本的ThreadPoolExecutor,接收處理任務(wù)的源頭;

? 2、scala.concurrent.ExecutionContext.Implicits.global:有意義的ForkJoin池,異步代碼主要結(jié)構(gòu)基礎(chǔ);

? 3、HBase異步客戶端的線程池Default-IPC-NioEventLoopGroup,不用也不行,HBase異步客戶端AsyncTable.putAll返回結(jié)果很簡陋只是個(gè)CompletableFuture<Void>,推測僅僅是用了個(gè)Runnable去做的,這樣的話在putAll當(dāng)中有任何異常都不是很好處理,所以感覺HBase異步客戶端并不是十分的完善。無意發(fā)現(xiàn)一個(gè)scala.concurrent.java8.FuturesConvertersImpl,不知道能不能做java Future和scala Future之間的轉(zhuǎn)換?

? 在scala線程的執(zhí)行過程內(nèi)部嵌套了HBase異步客戶端線程、使用了SI所以基于sprinBoot打包應(yīng)用,都沒有什么問題。現(xiàn)代java應(yīng)用因?yàn)橐蕾嚭芏啵鶗幸鉄o意帶了一堆線程池,有的你知道有的你不知道。這里我希望第一個(gè)接收任務(wù)的線程池不要被阻塞,所以所有的阻塞任務(wù)都放到了2和3,2需要做一些磁盤IO、3需要和HBase遠(yuǎn)程交互也就是網(wǎng)絡(luò)IO,測試也證實(shí)這種IO操作都是耗時(shí)大戶,有意無意用了三個(gè)不同的線程池,自然隔離出去這些阻塞操作,實(shí)現(xiàn)了艙壁模式,測試當(dāng)中1肯定不會被阻塞,只需要一兩個(gè)線程就可以保持保活的接收任務(wù)服務(wù)。

? 在scala Future的執(zhí)行過程中,有一些額外的中間結(jié)果希望委托給別的Future去單獨(dú)處理,也不需要處理結(jié)果也不對處理負(fù)責(zé),相當(dāng)于從一個(gè)Future A? Fork出另一個(gè)Future B去處理一些額外的中間結(jié)果,看了下scala Future的自帶方法沒有合適的,這種情況可以直接擼一個(gè)Future B分支去處理,F(xiàn)uture A仍然自己運(yùn)行自己的,沒什么問題,像這樣:

Future {

? Future B的任務(wù)...

}.recover[Boolean] {

? case t:Throwable =>println("A Throwable has occured!: " + t.getCause);false

? case e:Exception =>println("An Exception has occured!: " + e.getCause);false

? case er:Error =>println("An Error has occured!: " + er.getCause);false

}.onComplete {

? case Success(ok) =>if(ok) println("is Done") else println("So what?")

? case Failure(ko) =>println("Never "+ko)

}

? 類似的,CompletableFuture也有一個(gè)靜態(tài)方法runAsync可以直接去實(shí)現(xiàn)一個(gè)Fork & Forget分支,方便你直接開始一段異步任務(wù),但也是返回的CompletableFuture<Void>,不夠完善,相比之下Scala? Future的onComplete、recover給你提供了完備的異常處理。上述Future 分支在傳統(tǒng)java里相當(dāng)于:

new Thread(

??? new Runnable(){

??????? .......

??? }

).start

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,250評論 6 530
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 97,923評論 3 413
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,041評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,475評論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,253評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,801評論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,882評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,023評論 0 285
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,530評論 1 331
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,494評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,639評論 1 366
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,177評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,890評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,289評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,552評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,242評論 3 389
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,626評論 2 370