個人理解,如有錯誤,煩請指正!
在學習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()之后,可能會先去執行其他任務,並不會啥都不做等在原地。上文中糾結的問題,差距並不會太大。