来自java docs,
ForkJoinPool与其他类型的ExecutorService的不同之处主要在于使用工作窃取:池中的所有线程都试图查找并执行由其他活动任务创建的子任务(如果不存在则最终阻塞等待工作)。
这可以在大多数任务产生其他子任务时实现高效处理(与大多数ForkJoinTasks一样)。在构造函数中将asyncMode设置为true时,ForkJoinPools也可能适用于从未加入的事件样式任务。
通过下面的ForkJoinPool example后,与ThreadPoolExecutor不同,我没有看到设置队列大小的参数。我没有弄清楚ForkJoinPool如何窃取机制。
//creating the ThreadPoolExecutor
ThreadPoolExecutor executorPool = new ThreadPoolExecutor(2, 10, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(3000), threadFactory, rejectionHandler);
假设我已创建了包含10个线程的ThreadPoolExecutor,并且已提交了3000个Callable任务。这些线程如何共享子任务的执行负载?
对于相同的用例,ForkJoin池的行为方式有何不同?
答案 0 :(得分:9)
如果您事先有3000个任务,并且它们不会产生其他任务,那么这两个任务的行为将完全不同:使用10个线程,将一次运行10个任务,直到它们全部完成。
ForkJoinPool专为您开始执行一个或几个任务的情况而设计,但任务知道如何将自己拆分为子任务。在这种情况下,ForkJoinPool经过优化,允许任务检查处理线程的可用性并自行分解。
答案 1 :(得分:4)
在ForkJoinPool
中,有两种队列 - 在提交任务时基本上使用的池1和特定于线程的队列(即每个线程一个)。从ForkJoinTask
开始,您可以调用新任务(通常是问题的一部分)。
这些新任务不会提供给池队列,而是提供给特定于线程的队列。因此,它们优先于池1被取/拉,就好像你已完成同一任务中的所有工作一样。此外,调用任务似乎被阻止以完成子任务。
实际上,“阻塞时间”用于消耗子任务。让其他线程“闲逛”而其中一个被工作淹没将是愚蠢的。因此,“偷工作”发生了。
超越。为了提高效率,“偷工作”从相反的界限中获取/拉动任务。这大大减少了对队列编写的争用。
始终保持效率,最好只将问题分成两个子任务,然后让子任务一次又一次地分割。即使您知道问题必须直接分为N个部分。这是因为“工作窃取”需要并发写入共享资源,因此限制其激活和争用!