【Java并发编程线程池】 ForkJoinPool 线程池是什么 怎么工作的 和传统的ThreadPoolExecutor比较
Java 中的 ForkJoinPool 线程池是什么 怎么工作的
Java 中的 ForkJoinPool 线程池是什么 怎么工作的
相比较于传统的线程池,ForkJoinPool 线程池更适合处理大量的计算密集型任务,它的核心思想是将一个大任务拆分成多个小任务,然后将这些小任务分配给多个线程去执行,最后将这些小任务的结果合并起来,得到最终的结果。
工作窃取
值得注意的,ForkJoinPool中的每个线程都有自己的任务队列,当线程任务队列空了或者当前线程空闲,则会去别的线程的任务队列获取待执行任务,这个过程是工作窃取。
怎么工作的
(一):传统线程池
Java中传统线程池(ThreadPoolExecutor)的工作流程的描述:
以 new ThreadPoolExecutor(2, 4, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10)) 为例,线程池的工作流程如下:
代码用例:
public static void main1(String[] args) {
final var poolExecutor = new ThreadPoolExecutor(
2, // 核心线程数
4, // 最大线程数
60,// 线程空闲时间
TimeUnit.SECONDS, // 时间单位
new ArrayBlockingQueue<>(10), // 任务队列
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
// 定义一个Runnable
MyRunnable runnable = new MyRunnable();
// 提交任务
for (int i = 0; i < 10; i++) {
poolExecutor.submit(runnable);
}
}
static class MyRunnable implements Runnable {
@SneakyThrows
@Override
public void run() {
Thread.sleep(1000);
System.out.println("Hello, world!");
}
}
- 提交任务;
- 线程池中的线程数量小于 corePoolSize(核心线程数)时,不管有没有空闲的线程,都会创建一个新的线程来执行任务;
- 线程池中的线程数量等于 corePoolSize 时,若没有空闲线程,则任务会被放入任务队列中,等待线程池中的线程调度执行;
- 核心线程数已满 且 任务队列已满时,线程数量小于 maximumPoolSize(最大线程数)时,会创建新的线程来执行任务;
- 最后若是核心线程数、任务队列、最大线程数都满了,会根据拒绝策略来处理新任务。
图解:

(二):ForkJoinPool 线程池
ForkJoinPool工作流程的描述:
以new ForkJoinPool(4) 和 普通的任务 为例,ForkJoinPool的工作流程如下:
代码用例:
public static void main(String[] args) {
final var forkJoinPool = new ForkJoinPool(4);// 这个参数 “4” 的作用指定线程池的线程数量
// 定义一个Runnable
MyRunnable runnable = new MyRunnable();
// 提交任务
for (int i = 0; i < 10; i++) {
forkJoinPool.submit(runnable);
}
}
- 提交任务;(初次执行时,会做一些初始化工作,如创建任务队列的数组)
- 线程数量小于指定的线程数量时,会创建新的线程来执行任务,且每个线程都有自己的任务队列,区分于传统线程池只有一个任务队列,ForkJoinPool的线程池里的每个线程都有自己的任务队列;
- 每个线程从自己的任务队列中取出任务执行;
- 每个任务执行完毕后,且自己的任务队列为空时,会从其他线程的任务队列中偷取任务执行,这里被称作为“工作窃取”;
图解:

怎么体现将大任务拆分成小任务
在刚刚的代码用例中,我们提交了10个任务,但是我们的任务是一个普通的任务,没有体现将大任务拆分成小任务,下面我们换一个任务,体现将大任务拆分成小任务。
任务要求
要求计算 1 ~ 100w 的阶和,就是 "1+2+3+...+100w".
任务拆分方案:
我们可以将这个大任务拆分成多个小任务,将任务分配小到数的差值为10及以内时不再拆分,每个小任务计算区间的和,最后将这些小任务的结果合并起来,得到最终的结果。
如下
1:100w 的阶和 = 1w 的阶和 + 2w 的阶和 + 3w 的阶和 + ... + 10w 的阶和
2:10w 的阶和 = 1w 的阶和 + 2w 的阶和 + 3w 的阶和 + ... + 10w 的阶和
3:以此类推,直到区间的差值小于等于10时,不再拆分。
如100的阶和 = 1~10 的阶和 + 11~20 的阶和 + ... + 91~100 的阶和,其中 1~10的差值不大于10,不再拆分。
代码用例:
public static void main(String[] args) {
// 任务要求:计算 1 ~ 100w 的阶和,就是 "1+2+3+...+100w".
// 1. 创建一个 ForkJoinPool 对象
ForkJoinPool forkJoinPool = new ForkJoinPool(4);
// 2. 提交一个任务
Integer result = forkJoinPool.invoke(new MyTask(1, 1000000));
System.out.println("result = " + result);
}
private static class MyTask extends RecursiveTask<Integer> {
private static final int THRESHOLD = 10;// 拆分最小任务的阈值
private int begin;// 计算的区间的开始
private int end;// 计算的区间的结束
public MyTask(int begin, int end) {
this.begin = begin;
this.end = end;
}
@Override
protected Integer compute() {
if (end - begin <= THRESHOLD) {
int sum = 0;
for (int i = begin; i <= end; i++) {
sum += i;
}
return sum;
} else {
int mid = (begin + end) / 2;
MyTask left = new MyTask(begin, mid);
MyTask right = new MyTask(mid + 1, end);
left.fork();// 提交到任务队列
right.fork();
return left.join() + right.join();// join等待结果,线程会出现空闲,会去别的任务队列窃取任务执行
}
}
}
场景(常用于哪)
最常见、常用的场景中,其实就是在我们的Stream API中,我们使用了parallelStream()方法,底层就是使用了ForkJoinPool线程池。还有就是Java中新特性“虚拟线程”也是基于ForkJoinPool线程池实现的。
【Java并发编程线程池】 ForkJoinPool 线程池是什么 怎么工作的 和传统的ThreadPoolExecutor比较的更多相关文章
- [Java并发编程(二)] 线程池 FixedThreadPool、CachedThreadPool、ForkJoinPool?为后台任务选择合适的 Java executors
[Java并发编程(二)] 线程池 FixedThreadPool.CachedThreadPool.ForkJoinPool?为后台任务选择合适的 Java executors ... 摘要 Jav ...
- Java 并发编程——Executor框架和线程池原理
Eexecutor作为灵活且强大的异步执行框架,其支持多种不同类型的任务执行策略,提供了一种标准的方法将任务的提交过程和执行过程解耦开发,基于生产者-消费者模式,其提交任务的线程相当于生产者,执行任务 ...
- [Java并发编程(一)] 线程池 FixedThreadPool vs CachedThreadPool ...
[Java并发编程(一)] 线程池 FixedThreadPool vs CachedThreadPool ... 摘要 介绍 Java 并发包里的几个主要 ExecutorService . 正文 ...
- Java 并发编程——Executor框架和线程池原理
Java 并发编程系列文章 Java 并发基础——线程安全性 Java 并发编程——Callable+Future+FutureTask java 并发编程——Thread 源码重新学习 java并发 ...
- Java并发编程:如何创建线程?
Java并发编程:如何创建线程? 在前面一篇文章中已经讲述了在进程和线程的由来,今天就来讲一下在Java中如何创建线程,让线程去执行一个子任务.下面先讲述一下Java中的应用程序和进程相关的概念知识, ...
- 【转】Java并发编程:如何创建线程?
一.Java中关于应用程序和进程相关的概念 在Java中,一个应用程序对应着一个JVM实例(也有地方称为JVM进程),一般来说名字默认是java.exe或者javaw.exe(windows下可以通过 ...
- 2、Java并发编程:如何创建线程
Java并发编程:如何创建线程? 在前面一篇文章中已经讲述了在进程和线程的由来,今天就来讲一下在Java中如何创建线程,让线程去执行一个子任务.下面先讲述一下Java中的应用程序和进程相关的概念知识, ...
- 原创】Java并发编程系列2:线程概念与基础操作
[原创]Java并发编程系列2:线程概念与基础操作 伟大的理想只有经过忘我的斗争和牺牲才能胜利实现. 本篇为[Dali王的技术博客]Java并发编程系列第二篇,讲讲有关线程的那些事儿.主要内容是如下这 ...
- Java并发编程之深入理解线程池原理及实现
Java线程池在实际的应用开发中十分广泛.虽然Java1.5之后在JUC包中提供了内置线程池可以拿来就用,但是这之前仍有许多老的应用和系统是需要程序员自己开发的.因此,基于线程池的需求背景.技术要求了 ...
- Java并发编程、多线程、线程池…
<实战java高并发程序设计>源码整理https://github.com/petercao/concurrent-programming/blob/master/README.md Ja ...
随机推荐
- 多元/多维高斯/正态分布概率密度函数推导 (Derivation of the Multivariate/Multidimensional Normal/Gaussian Density)
各种维度正态分布公式: 一维正态分布 二维正态分布/多维正态分布 各向同性正态分布 注:即方差都是一样的,均值不一样,方差的值可以单独用标量表示. 多元/多维高斯/正态分布概率密度函数推导 (Deri ...
- Sql高级
sql高级 1. 索引与视图 常见的数据结构 栈:先进后出 队列:先进先出 数组:查询快,根据下标查询 链表:分为双链表与单链表.单链表指向下一个数据的存储位置:双链表指向前一个与下一个数据的存储位置 ...
- C++之OpenCV入门到提高001:使用 Visual Studio2022 配置 OpenCV 环境
一.介绍 从今天开始,我们又要开始一个新的系列了,这个系列就是<C++之 Opencv 入门到提高>.这个系列是有关如何使用 C++ 语言,通过 Opencv 来实现图像处理.缺陷检测.视 ...
- Typecho框架个人博客搭建方法学习
使用Typecho框架一个月又十二天了,就目前感觉来说,整体还不错,很多方面都支持个性化,二次开发,但是目前MD编辑器有一丢丢问题,不能同步滚动条滚动,就是编辑器区域滚动,预览区域没有动静,需要两边都 ...
- 题解:CF507C Guess Your Way Out!
CF507C Guess Your Way Out! 题解 算法 模拟 思路 按照左右左右的方式先往下找,每次找到一个子节点时就看此节点为根的子树是否包含目标节点,如果包含就继续往下走,不包含说明目标 ...
- IDEA方法模板、类模板设置记录
类模板 设置入口 Mac:common+,进入设置->Editor->File and Code Templates->Includes 模版代码 /** * todo * * @a ...
- Vulhub WebLogic漏洞复现
目录 前言 任意文件上传漏洞(CVE-2018-2894) 管理控制台未授权RCE漏洞(CVE-2020-14882 & CVE-2020-14883) 未授权RCE漏洞(CVE-2023-2 ...
- 基于nginx的tomcat负载均衡和集群(超简单)
今天看到"基于apache的tomcat负载均衡和集群配置 "这篇文章成为javaEye热点. 略看了一下,感觉太复杂,要配置的东西太多,因此在这里写出一种更简洁的方法. 要集群t ...
- Mybatis【3】-- Mybatis使用工具类读取配置文件以及从属性读取DB信息
代码直接放在Github仓库[https://github.com/Damaer/Mybatis-Learning ],可直接运行,就不占篇幅了. 1.使用工具类获取sqlSession实例对象 在上 ...
- 记录一次C#爬虫记录,获取必应图片
记录一次C#爬虫记录,获取必应图片 起因 事情是这样的,我创建了一个仓库,里面有2018年到目前为止每日的必应壁纸,在八月份的时候我看到微软有接口文档,于是写了一个服务,每天早上八点钟会获取必应壁纸( ...