Fork/Join模式中fork和invokeAll區別的個人理解


個人理解,如有錯誤,煩請指正!

在學習Fork/Join模式的時候,看到網上有人拆分子任務的時候有兩種寫法:

t1.fork();
t2.fork();
invokeAll(t1,t2);

然后開始好奇這兩種寫法的區別,通過搜索和閱讀源碼發現是有一點區別的, 兩次fork的性能可能不如invokeAll來的好。

首先我們來看看fork()的源碼長什么樣:

public final ForkJoinTask<V> fork(){
        Thread t;
        if((t=Thread.currentThread())instanceof ForkJoinWorkerThread)
        ((ForkJoinWorkerThread)t).workQueue.push(this);
        else
        ForkJoinPool.common.externalPush(this);
        return this;
}

發現fork是直接將子任務放到當前線程的任務隊列中了。 而再看invokeAll:

public static void invokeAll(ForkJoinTask<?> t1,ForkJoinTask<?> t2){
        int s1,s2;
        t2.fork();
        if(((s1=t1.doInvoke())&ABNORMAL)!=0)
        t1.reportException(s1);
        if(((s2=t2.doJoin())&ABNORMAL)!=0)
        t2.reportException(s2);
}

private int doInvoke() {
        int s; Thread t; ForkJoinWorkerThread wt;
        return (s = doExec()) < 0 ? s :
        ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
        (wt = (ForkJoinWorkerThread)t).pool.
        awaitJoin(wt.workQueue, this, 0L) :
        externalAwaitDone();
}

對t2進行fork之后,調用了所屬ForkJoinPool的awaitJoin方法,該方法幫助安排其他線程來運行這個任務(個人理解)。

因此,理論上來說,invokeAll會比單獨fork效率要高一點。

插入一個Fork/Join這種模式的原理:

這種方法被稱為工作密取(work stealing)。每個工作線程都有一個雙端隊列(deque)來完成任務。一個工作線程將子任務壓人其雙端隊列的隊頭。(只有一個線程可以訪問隊頭,所以不需要加鎖。)一個工作線程空閑時,它會從另一個雙端隊列的隊尾“ 密取” 一個任務。由於大的子任務都在隊尾,這種密取很少出現。

所以,fork之后,如果當前隊列有多余任務未完成,其他閑着的線程因該也會過來領任務,差距應該也不會太大。

另外,網上說的通過fork之后,當前線程只是起到監督的作用,而invokeAll后當前線程也會參與工作的說法。經過測試,似乎並未發現當前線程閑着,也許是我的測試方法不對。

總結

總之,剛開始學習並發,還不理解很多東西,還需要加倍努力啊!!!!好多源碼也看不太明白。

2021-01-15

Fork/Join模式主要思想是分而治之,將一個大任務拆分成很多小任務,讓多個線程去做。

jdk提供的ForkJoinPool和ThreadPool是有區別的,ForkJoinPool中的線程在遇到阻塞或空閑的時候,會去執行自己隊列的下一個任務,如果自己任務隊列是空的,則會去其他線程的任務列里“竊取”一個任務出來做。

因此:Thread.join()后線程會阻塞到任務完成,但是RecursiveAction/RecursiveTask.join()之后,可能會先去執行其他任務,並不會啥都不做等在原地。上文中糾結的問題,差距並不會太大。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM