我可以通过提供代码段来解释我的问题:
public static void main(final String[] a) {
Stream.of(1, 2, 3, 4).map(i -> ForkJoinPool.commonPool().submit(new RecursiveAction() {
@Override
protected void compute() {
System.out.println(Thread.currentThread());
}
})).forEach(ForkJoinTask::join);
}
在具有4个核心的笔记本电脑上运行时,会打印出来:
Thread[main,5,main]
Thread[ForkJoinPool.commonPool-worker-1,5,main]
Thread[main,5,main]
Thread[ForkJoinPool.commonPool-worker-1,5,main]
为什么某些任务在主线程中运行,主线程是公共fork连接线程池之外的线程?
创建自定义fork join线程池时,不会发生这种情况:
public static void main(final String[] a) {
final ForkJoinPool p = new ForkJoinPool(4);
Stream.of(1, 2, 3, 4).map(index -> p.submit(new RecursiveAction() {
@Override
protected void compute() {
System.out.println(Thread.currentThread());
}
})).forEach(ForkJoinTask::join);
}
Thread[ForkJoinPool-1-worker-1,5,main]
Thread[ForkJoinPool-1-worker-1,5,main]
Thread[ForkJoinPool-1-worker-1,5,main]
Thread[ForkJoinPool-1-worker-1,5,main]
那么,换句话说,公共池有什么特别之处?提供这些知识,在公共池中执行长时间运行的任务是明智的还是不明智的想法?
答案 0 :(得分:5)
发生了一些相当聪明的事情。
当你从一个不在线程中的线程调用ForkJoinTask::join
时,它会显示(来自ForkJoinPool.awaitJoin
上的注释)当前线程可以“帮助”执行任务。
这就是主线程在fork-join池中执行任务的原因。
但是为什么在创建自定义池的情况下会有所不同?好吧,我的猜测是你的自定义池有足够的线程,不需要主线程。因为另一件事是默认情况下创建的“公共池”的一个线程少于可用处理器的数量。
有关详细信息,请查看源代码。请注意,此行为未在javadocs中指定,因此在将来的实现中可能会更改。
答案 1 :(得分:3)
阐述我的其他评论:
将提交线程用作工作线程一直是关于性能的。在工作窃取优先程序中,提交线程将新请求放入提交队列,并通知工作线程有工作。 (确切地知道哪些线程得到通知已经随着时间而改变。)
工作窃取线程只能通过窃取其他线程的deques或转到提交队列来获得工作。 (现在有多个提交队列,因为仅使用一个扩展问题。)线程如何找到工作是无关紧要的。相关的是它很慢。线程需要醒来然后去寻找工作。因为线程的队列是deques,所以无法为任何线程提供工作。这种设计的主要原因是作者从Cilk复制了设计。
Cilk是集群fork / join程序。它主要在计算机在网络中连接的集群环境中工作。拥有工作共享算法,其中计算机查询其他计算机的队列以查看工作的位置,或者将分叉任务放入哪里(进入具有最少数量的待处理任务以进行负载平衡的队列)是令人望而却步的。因此,在集群环境中,首先使用deques进行工作窃取。
在Java中,环境是共享内存。其他线程的队列中存在微不足道的开销。在任何情况下,对框架的第一个请求都要求从线程中唤醒,寻找工作并且速度很慢。
对于Java8并行流,系统需要公共池和加速进程的方法。 (并行线程的初始时间非常糟糕。)因此,作者想出了将提交线程用作工作者的想法。但是,这种技术引入了自己的问题,如下所述:http://coopsoft.com/ar/Calamity2Article.html#submission