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