ForkJoinPool和ThreadPoolExecutor区别是什么?

2023-12-21 02:04

本文主要是介绍ForkJoinPool和ThreadPoolExecutor区别是什么?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

ForkJoinPool和ThreadPoolExecutor的主要区别体现在任务执行的方式和适用的场景上。

  1. 任务执行方式:ThreadPoolExecutor是共享队列,所有任务都在一个队列中等待执行。而ForkJoinPool对于每个并行度都有独立的队列,每个任务都会被分配到对应的队列中执行。
  2. 适用场景:如果需要执行大量独立的任务,且每个任务都比较短,那么ThreadPoolExecutor可能更适合,因为所有任务都在一个队列中等待,可以减少线程的创建和销毁的开销。但是,如果需要执行大任务,且该任务可以被分割成许多小任务并行执行,那么ForkJoinPool可能更适合,因为每个小任务都会被分配到对应的队列中执行,可以充分利用多核处理器的优势。
    总的来说,ForkJoinPool和ThreadPoolExecutor各有优势,选择哪种方式取决于具体的应用场景和需求。

ForkJoinPool和ThreadPoolExecutor都是Java中用于实现线程池的类,但它们有以下几点区别:

  1. 任务分解方式不同:ForkJoinPool使用"分而治之"(Divide and Conquer)的思想,将大任务划分为多个子任务,并且子任务之间可能存在依赖关系,最终汇总子任务的结果得到大任务的结果。而ThreadPoolExecutor则是使用固定大小的线程池来执行一系列独立的任务。
  2. 内部工作机制不同:ForkJoinPool使用工作窃取(Work Stealing)算法来提高性能,即当一个线程执行完自己的任务队列后,会尝试从其他线程的任务队列中"窃取"(Steal)任务执行;而ThreadPoolExecutor是采用传统的线程池模型,通过内部的阻塞队列来管理任务。
  3. 上下文切换的开销不同:ForkJoinPool在任务分解时可以避免不必要的上下文切换,因此在某些情况下比ThreadPoolExecutor性能更好。但是,如果任务本身非常小,那么这种优势可能会被消耗掉。
    总的来说,ForkJoinPool适用于处理需要递归分解的任务,例如归并排序、快速排序等算法;而ThreadPoolExecutor适用于处理大量的独立任务,例如Web服务器中的请求处理。

ThreadPoolExecutor

首先,ThreadPoolExecutor的内部主要包含以下几个重要的组成部分:
workers:这是一个包含所有工作线程的集合。
workQueue:这是一个阻塞队列,用于存储待处理的任务。
ctl:这是一个原子整数,用于控制线程池的状态和工作线程的数量。
当你创建一个ThreadPoolExecutor时,你可以指定线程池的核心线程数(corePoolSize)、最大线程数(maximumPoolSize)、空闲线程的存活时间(keepAliveTime)、时间单位(unit)、工作队列(workQueue)以及线程工厂(threadFactory)。
当你提交一个新的任务(Runnable)给ThreadPoolExecutor时,以下是其大致的处理逻辑:
如果当前的工作线程数少于corePoolSize,那么ThreadPoolExecutor会创建一个新的工作线程来执行任务。
如果当前的工作线程数已经达到corePoolSize,那么ThreadPoolExecutor会尝试将任务添加到workQueue中。
如果workQueue已满,那么ThreadPoolExecutor会尝试创建一个新的工作线程来执行任务,只要当前的工作线程数还没有达到maximumPoolSize。
如果当前的工作线程数已经达到maximumPoolSize,那么ThreadPoolExecutor会拒绝这个任务,具体的拒绝策略由RejectedExecutionHandler决定。
在执行任务时,每个工作线程都会运行一个无限循环,不断地从workQueue中取出任务并执行。如果workQueue为空,那么线程会等待一段时间(keepAliveTime),如果在这段时间内仍没有新的任务,那么线程就会被终止,除非当前的工作线程数少于corePoolSize。
以上只是ThreadPoolExecutor的简单解析,其实际的实现包含了很多其他的细节和优化,例如如何正确地管理线程的生命周期,如何高效地处理任务队列,如何处理异常等等。如果你想深入理解ThreadPoolExecutor,我建议你直接阅读其源码,并参考相关的文档和教程。

ForkJoinPool

ForkJoinPool是Java中的一个线程池实现,它专门用于执行分而治之的任务。它是Java 7中引入的,并在Java 8中得到了进一步改进。ForkJoinPool的设计目标是为了高效地执行递归可分解的任务,并利用多核处理器的并行性。
ForkJoinPool的核心思想是将大型任务划分为更小的子任务,然后将这些子任务分配给工作线程执行。当一个工作线程完成了它分配的任务后,它可以从其他工作线程的任务队列中偷取任务来执行,以实现负载均衡。
下面是ForkJoinPool的一些关键概念和特性:

工作窃取(Work Stealing):ForkJoinPool中的工作线程在执行完自己的任务后,可以从其他线程的任务队列中窃取任务来执行。这种机制能够使得工作线程在负载不均衡的情况下自适应地获取更多任务,提高线程利用率和任务执行效率。
分而治之(Divide and Conquer):ForkJoinPool适用于递归可分解的任务。大型任务会被划分为更小的子任务,这些子任务可能进一步划分为更小的子任务,直到达到某个任务不可再分的阈值。然后,工作线程会执行这些子任务,并将结果合并起来。
工作窃取队列(Work Stealing Queue):每个工作线程都有一个自己的工作窃取队列,用于存储待执行的任务。当一个工作线程需要获取任务时,它首先从自己的队列中获取,如果队列为空,则从其他工作线程的队列中窃取任务。
并行性控制:ForkJoinPool提供了一些方法来控制并行执行的级别。例如,可以使用ForkJoinPool.commonPool()方法获取一个默认的共享线程池,也可以创建自定义的ForkJoinPool实例,并设置并行度(parallelism)来控制并行执行的线程数。

使用ForkJoinPool时,需要创建继承自RecursiveTask或RecursiveAction的任务类,并实现compute()方法来定义任务的执行逻辑。RecursiveTask用于有返回值的任务,而RecursiveAction用于没有返回值的任务。在compute()方法中,可以判断任务是否足够小,如果足够小则直接执行任务,否则将任务划分为更小的子任务并提交给ForkJoinPool。
下面是一个简单的示例,演示了如何使用ForkJoinPool来计算斐波那契数列:

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;public class FibonacciTask extends RecursiveTask<Integer> {private final int n;public FibonacciTask(int n) {this.n = n;}@Overrideprotected Integer compute() {if (n <= 1) {return n;} else {FibonacciTask task1 = new FibonacciTask(n - 1);task1.fork();FibonacciTask task2 = new FibonacciTask(n - 2);return task2.compute() + task1.join();}}public static void main(String[] args) {ForkJoinPool forkJoinPool = ForkJoinPool.commonPool();FibonacciTask task = new FibonacciTask(10);int result = forkJoinPool.invoke(task);System.out.println("Result: " + result);}
}

在这个示例中,FibonacciTask继承自RecursiveTask,表示一个有返回值的任务。在compute()方法中,我们首先判断n是否足够小,如果是,则直接返回n的值。否则,我们创建两个新的FibonacciTask实例,分别用于计算n-1和n-2的斐波那契数,并通过fork()方法提交给ForkJoinPool进行并行执行。然后,我们使用join()方法等待并获取子任务的结果,并将它们相加作为当前任务的结果。
在main()方法中,我们使用ForkJoinPool.commonPool()获取一个默认的共享线程池,并创建一个FibonacciTask实例来计算斐波那契数列的第10项。最后,我们通过invoke()方法提交任务给ForkJoinPool,并获取计算结果进行输出。
这只是ForkJoinPool的一个简单示例,它展示了ForkJoinPool的基本用法和特性。在实际应用中,你可能需要根据具体需求进行更复杂的任务划分和逻辑处理。ForkJoinPool和ThreadPoolExecutor都是Java中的线程池实现,但它们的设计目标和使用场景有所不同。
区别
总结一下,这两个线程池的主要区别在于:
● ThreadPoolExecutor适用于大量短生命周期的任务,而ForkJoinPool适用于计算密集型并且可以并行处理的任务。
● ForkJoinPool使用了工作窃取算法,可以减少线程间的竞争,提高CPU的利用率。

这篇关于ForkJoinPool和ThreadPoolExecutor区别是什么?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/518308

相关文章

native和static native区别

本文基于Hello JNI  如有疑惑,请看之前几篇文章。 native 与 static native java中 public native String helloJni();public native static String helloJniStatic();1212 JNI中 JNIEXPORT jstring JNICALL Java_com_test_g

Android fill_parent、match_parent、wrap_content三者的作用及区别

这三个属性都是用来适应视图的水平或者垂直大小,以视图的内容或尺寸为基础的布局,比精确的指定视图的范围更加方便。 1、fill_parent 设置一个视图的布局为fill_parent将强制性的使视图扩展至它父元素的大小 2、match_parent 和fill_parent一样,从字面上的意思match_parent更贴切一些,于是从2.2开始,两个属性都可以使用,但2.3版本以后的建议使

Collection List Set Map的区别和联系

Collection List Set Map的区别和联系 这些都代表了Java中的集合,这里主要从其元素是否有序,是否可重复来进行区别记忆,以便恰当地使用,当然还存在同步方面的差异,见上一篇相关文章。 有序否 允许元素重复否 Collection 否 是 List 是 是 Set AbstractSet 否

javascript中break与continue的区别

在javascript中,break是结束整个循环,break下面的语句不再执行了 for(let i=1;i<=5;i++){if(i===3){break}document.write(i) } 上面的代码中,当i=1时,执行打印输出语句,当i=2时,执行打印输出语句,当i=3时,遇到break了,整个循环就结束了。 执行结果是12 continue语句是停止当前循环,返回从头开始。

maven发布项目到私服-snapshot快照库和release发布库的区别和作用及maven常用命令

maven发布项目到私服-snapshot快照库和release发布库的区别和作用及maven常用命令 在日常的工作中由于各种原因,会出现这样一种情况,某些项目并没有打包至mvnrepository。如果采用原始直接打包放到lib目录的方式进行处理,便对项目的管理带来一些不必要的麻烦。例如版本升级后需要重新打包并,替换原有jar包等等一些额外的工作量和麻烦。为了避免这些不必要的麻烦,通常我们

ActiveMQ—Queue与Topic区别

Queue与Topic区别 转自:http://blog.csdn.net/qq_21033663/article/details/52458305 队列(Queue)和主题(Topic)是JMS支持的两种消息传递模型:         1、点对点(point-to-point,简称PTP)Queue消息传递模型:         通过该消息传递模型,一个应用程序(即消息生产者)可以

深入探讨:ECMAScript与JavaScript的区别

在前端开发的世界中,JavaScript无疑是最受欢迎的编程语言之一。然而,很多开发者在使用JavaScript时,可能并不清楚ECMAScript与JavaScript之间的关系和区别。本文将深入探讨这两者的不同之处,并通过案例帮助大家更好地理解。 一、什么是ECMAScript? ECMAScript(简称ES)是一种脚本语言的标准,由ECMA国际组织制定。它定义了语言的语法、类型、语句、

Lua 脚本在 Redis 中执行时的原子性以及与redis的事务的区别

在 Redis 中,Lua 脚本具有原子性是因为 Redis 保证在执行脚本时,脚本中的所有操作都会被当作一个不可分割的整体。具体来说,Redis 使用单线程的执行模型来处理命令,因此当 Lua 脚本在 Redis 中执行时,不会有其他命令打断脚本的执行过程。脚本中的所有操作都将连续执行,直到脚本执行完成后,Redis 才会继续处理其他客户端的请求。 Lua 脚本在 Redis 中原子性的原因

msys2 minggw-w64 cygwin wsl区别

1 mingw-w64,这是gcc一直win平台下产生的,所以是win版的gcc,既支持32也支持64bit 2cygwin专注于原样在windows上构建unix软件, 3msys让Linux开发者在windows上运行软件,msys2专注于构建针对windows api构建的本机软件 4 wsl  windows subsystem for linux 是一个在windows 10 上能

【Java中的位运算和逻辑运算详解及其区别】

Java中的位运算和逻辑运算详解及其区别 在 Java 编程中,位运算和逻辑运算是常见的两种操作类型。位运算用于操作整数的二进制位,而逻辑运算则是处理布尔值 (boolean) 的运算。本文将详细讲解这两种运算及其主要区别,并给出相应示例。 应用场景了解 位运算和逻辑运算的设计初衷源自计算机底层硬件和逻辑运算的需求,它们分别针对不同的处理对象和场景。以下是它们设计的初始目的简介: