只聽(tīng)到P8大佬不急不慢問(wèn)道:談?wù)剬?duì)JDK并發(fā)工具的認(rèn)識(shí)?

我開(kāi)始仔細(xì)梳理多年的并發(fā)八股文經(jīng)驗(yàn),道:
線程池、Future、CompletableFuture和CompletionService這些并發(fā)工具都是幫助SE站在任務(wù)角度解決并發(fā)問(wèn)題,而非糾結(jié)于線程之間協(xié)作的細(xì)節(jié),比如線程之間如何實(shí)現(xiàn)等待、通知。
簡(jiǎn)單并行任務(wù)
線程池+Future 組合拳
任務(wù)間有聚合關(guān)系
AND、OR聚合,CompletableFuture 一招鮮
批量的并行任務(wù)
CompletionService 一把梭
并發(fā)編程可分為三個(gè)層面問(wèn)題:分工、協(xié)作、互斥。
當(dāng)關(guān)注于任務(wù)時(shí),你會(huì)發(fā)現(xiàn)你的視角已躍出并發(fā)編程細(xì)節(jié),而使用現(xiàn)實(shí)世界思維模式,類比現(xiàn)實(shí)世界的分工,其實(shí)線程池、Future、CompletableFuture和CompletionService都可列為分工問(wèn)題。
這三種任務(wù)模型,基本覆蓋日常工作中的并發(fā)場(chǎng)景,但其實(shí)還有一種“分治”任務(wù)模型。
分治,分而治之,一種解決復(fù)雜問(wèn)題的思維方法和模式。把一個(gè)復(fù)雜問(wèn)題分解成多個(gè)相似的子問(wèn)題,然后再把子問(wèn)題分解成更小的子問(wèn)題,直到子問(wèn)題簡(jiǎn)單到可以直接求解。理論上解決每一個(gè)問(wèn)題都對(duì)應(yīng)著一個(gè)任務(wù),所以對(duì)于問(wèn)題的分治,實(shí)際上就是對(duì)于任務(wù)的分治。

P8 大佬直接開(kāi)問(wèn),那你說(shuō)說(shuō)什么是分治任務(wù)模型?
分治任務(wù)模型可分為兩個(gè)階段:
就像官僚制度一樣:

那你平時(shí)開(kāi)發(fā)是如何使用Fork/Join的?

我,我平時(shí)還真沒(méi)通過(guò)啊,就背過(guò)。還好這道題,我面試前也準(zhǔn)備了…
Fork/Join是一個(gè)并行計(jì)算框架,以支持分治任務(wù)模型
Fork/Join計(jì)算框架主要包含兩部分:
分治任務(wù)的線程池ForkJoinPool
分治任務(wù)ForkJoinTask
這倆的關(guān)系類似于 ThreadPoolExecutor 和 Runnable,都是提交任務(wù)到線程池,只不過(guò)分治任務(wù)有自己獨(dú)特的任務(wù)類型ForkJoinTask。
ForkJoinTask
JDK7 提供,一個(gè)抽象類,核心方法如下:
ForkJoinTask有兩個(gè)子類——RecursiveAction和RecursiveTask,顯然都是用遞歸處理分治任務(wù)。這兩個(gè)子類都定義了抽象方法compute():
注意到這倆類都是抽象類,使用要定義子類實(shí)現(xiàn)。


只見(jiàn) P8 開(kāi)始冷笑,看來(lái)要問(wèn)源碼級(jí)別原理了!

那你說(shuō)下Fork/Join的工作原理
還好我知道阿里面試套路,凡是 java 工具,必問(wèn)深入的源碼。
因?yàn)镕ork/Join的核心就是ForkJoinPool,讓我來(lái)深入講解ForkJoinPool原理。
ThreadPoolExecutor本質(zhì)是個(gè)生產(chǎn)者-消費(fèi)者實(shí)現(xiàn),內(nèi)部有一個(gè)任務(wù)隊(duì)列,作為生產(chǎn)者和消費(fèi)者的通信媒介。ThreadPoolExecutor可以有多個(gè)工作線程,這些工作線程都共享一個(gè)任務(wù)隊(duì)列。
ForkJoinPool本質(zhì)上也是一個(gè)生產(chǎn)者-消費(fèi)者的實(shí)現(xiàn),但更智能
ForkJoinPool工作原理圖

ThreadPoolExecutor內(nèi)部只有一個(gè)任務(wù)隊(duì)列,而ForkJoinPool內(nèi)部有多個(gè)任務(wù)隊(duì)列,當(dāng)調(diào)用ForkJoinPool#invoke()或submit()提交任務(wù)時(shí),F(xiàn)orkJoinPool把任務(wù)通過(guò)路由規(guī)則提交到一個(gè)任務(wù)隊(duì)列,如果任務(wù)在執(zhí)行過(guò)程中會(huì)創(chuàng)建出子任務(wù),那么子任務(wù)會(huì)提交到工作線程對(duì)應(yīng)的任務(wù)隊(duì)列。
如果工作線程對(duì)應(yīng)的任務(wù)隊(duì)列空,是不是就沒(méi)活兒干了?
No!ForkJoinPool有個(gè)“任務(wù)竊取”機(jī)制,若工作線程空閑了,它會(huì)“竊取”其他工作任務(wù)隊(duì)列里的任務(wù),例如剛才那個(gè)圖中,線程T2對(duì)應(yīng)任務(wù)隊(duì)列已空

那它會(huì)“竊取”線程T1對(duì)應(yīng)的任務(wù)隊(duì)列的任務(wù)。這樣所有工作線程都不會(huì)閑著。
ForkJoinPool的任務(wù)隊(duì)列采用的是雙端隊(duì)列,工作線程正常獲取任務(wù)和“竊取任務(wù)”分別從任務(wù)隊(duì)列不同的端消費(fèi),這也能避免很多不必要的數(shù)據(jù)競(jìng)爭(zhēng)。
ForkJoinPool支持任務(wù)竊取機(jī)制,能夠讓所有線程的工作量基本公平,不會(huì)出現(xiàn)線程有的很忙,有的一直在摸魚,所以性能很好,是個(gè)很公正的領(lǐng)導(dǎo)。
Java8的Stream API里面并行流也是基于ForkJoinPool。
默認(rèn),所有的并行流計(jì)算都共享一個(gè)ForkJoinPool,這個(gè)共享的ForkJoinPool的默認(rèn)線程數(shù)是CPU核數(shù);
若所有并行流計(jì)算都是CPU密集型,完全沒(méi)有問(wèn)題,但若存在I/O密集型并行流計(jì)算,那很可能因?yàn)橐粋€(gè)很慢的I/O計(jì)算而拖慢整個(gè)系統(tǒng)的性能。所以建議用不同F(xiàn)orkJoinPool執(zhí)行不同類型的計(jì)算任務(wù)。
參考