多线程 ForkJoinPool
阅读目录
背景:ForkJoinPool的优势在于,可以充分利用多cpu,多核cpu的优势,把一个任务拆分成多个“小任务”,把多个“小任务”放到多个处理器核心上并行执行;当多个“小任务”执行完成之后,再将这些执行结果合并起来即可。这种思想值得学习。
主要参考《疯狂java讲义》
使用
Java7 提供了ForkJoinPool来支持将一个任务拆分成多个“小任务”并行计算,再把多个“小任务”的结果合并成总的计算结果。
ForkJoinPool是ExecutorService的实现类,因此是一种特殊的线程池。
使用方法:创建了ForkJoinPool实例之后,就可以调用ForkJoinPool的submit(ForkJoinTask<T> task) 或invoke(ForkJoinTask<T> task)方法来执行指定任务了。
其中ForkJoinTask代表一个可以并行、合并的任务。ForkJoinTask是一个抽象类,它还有两个抽象子类:RecusiveAction和RecusiveTask。其中RecusiveTask代表有返回值的任务,而RecusiveAction代表没有返回值的任务。
下面的UML类图显示了ForkJoinPool、ForkJoinTask之间的关系:

举例
以还行没有返回值的“大任务”(简单低打印1~300的数值)为例,程序将一个“大任务”拆分成多个“小任务”,并将任务交给ForkJoinPool来执行

/**
* Project Name:Spring0725
* File Name:ForkJoinPoolAction.java
* Package Name:work1201.basic
* Date:2017年12月4日下午2:26:55
* Copyright (c) 2017, 深圳金融电子结算中心 All Rights Reserved.
*
*/ package work1201.basic; import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.TimeUnit; /**
* ClassName:ForkJoinPoolAction <br/>
* Function: 使用ForkJoinPool完成一个任务的分段执行
* 简单的打印0-300的数值。用多线程实现并行执行
* Date: 2017年12月4日 下午2:26:55 <br/>
* @author prd-lxw
* @version 1.0
* @since JDK 1.7
* @see
*/
public class ForkJoinPoolAction { public static void main(String[] args) throws Exception{
PrintTask task = new PrintTask(0, 300);
//创建实例,并执行分割任务
ForkJoinPool pool = new ForkJoinPool();
pool.submit(task);
//线程阻塞,等待所有任务完成
pool.awaitTermination(2, TimeUnit.SECONDS);
pool.shutdown();
}
} /**
* ClassName: PrintTask <br/>
* Function: 继承RecursiveAction来实现“可分解”的任务。
* date: 2017年12月4日 下午5:17:41 <br/>
*
* @author prd-lxw
* @version 1.0
* @since JDK 1.7
*/
class PrintTask extends RecursiveAction{
private static final int THRESHOLD = 50; //最多只能打印50个数
private int start;
private int end; public PrintTask(int start, int end) {
super();
this.start = start;
this.end = end;
} @Override
protected void compute() { if(end - start < THRESHOLD){
for(int i=start;i<end;i++){
System.out.println(Thread.currentThread().getName()+"的i值:"+i);
}
}else {
int middle =(start+end)/2;
PrintTask left = new PrintTask(start, middle);
PrintTask right = new PrintTask(middle, end);
//并行执行两个“小任务”
left.fork();
right.fork();
} } }

执行结果:

ForkJoinPool-1-worker-1的i值:262
ForkJoinPool-1-worker-7的i值:75
ForkJoinPool-1-worker-7的i值:76
ForkJoinPool-1-worker-5的i值:225
ForkJoinPool-1-worker-3的i值:187
ForkJoinPool-1-worker-6的i值:150
ForkJoinPool-1-worker-6的i值:151
ForkJoinPool-1-worker-6的i值:152
ForkJoinPool-1-worker-6的i值:153
ForkJoinPool-1-worker-6的i值:154
......

因为我的电脑是i7处理器,一共8个cpu,观察线程的名称可以发现,8个cpu都在运行。
通过RecursiveTask的返回值,来对一个长度为100的数组元素进行累加。

/**
* Project Name:Spring0725
* File Name:ForJoinPollTask.java
* Package Name:work1201.basic
* Date:2017年12月4日下午5:41:46
* Copyright (c) 2017, 深圳金融电子结算中心 All Rights Reserved.
*
*/ package work1201.basic; import java.util.Random;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask; /**
* ClassName:ForJoinPollTask <br/>
* Function: 对一个长度为100的元素值进行累加
* Date: 2017年12月4日 下午5:41:46 <br/>
* @author prd-lxw
* @version 1.0
* @since JDK 1.7
* @see
*/
public class ForJoinPollTask { public static void main(String[] args) throws Exception {
int[] arr = new int[100];
Random random = new Random();
int total =0;
//初始化100个数组元素
for(int i=0,len = arr.length;i<len;i++){
int temp = random.nextInt(20);
//对数组元素赋值,并将数组元素的值添加到sum总和中
total += (arr[i]=temp);
}
System.out.println("初始化数组总和:"+total);
SumTask task = new SumTask(arr, 0, arr.length);
// 创建一个通用池,这个是jdk1.8提供的功能
ForkJoinPool pool = ForkJoinPool.commonPool();
Future<Integer> future = pool.submit(task); //提交分解的SumTask 任务
System.out.println("多线程执行结果:"+future.get());
pool.shutdown(); //关闭线程池 } } /**
* ClassName: SumTask <br/>
* Function: 继承抽象类RecursiveTask,通过返回的结果,来实现数组的多线程分段累累加
* RecursiveTask 具有返回值
* date: 2017年12月4日 下午6:08:11 <br/>
*
* @author prd-lxw
* @version 1.0
* @since JDK 1.7
*/
class SumTask extends RecursiveTask<Integer>{
private static final int THRESHOLD = 20; //每个小任务 最多只累加20个数
private int arry[];
private int start;
private int end; /**
* Creates a new instance of SumTask.
* 累加从start到end的arry数组
* @param arry
* @param start
* @param end
*/
public SumTask(int[] arry, int start, int end) {
super();
this.arry = arry;
this.start = start;
this.end = end;
} @Override
protected Integer compute() {
int sum =0;
//当end与start之间的差小于threshold时,开始进行实际的累加
if(end - start <THRESHOLD){
for(int i= start;i<end;i++){
sum += arry[i];
}
return sum;
}else {//当end与start之间的差大于threshold,即要累加的数超过20个时候,将大任务分解成小任务
int middle = (start+ end)/2;
SumTask left = new SumTask(arry, start, middle);
SumTask right = new SumTask(arry, middle, end);
//并行执行两个 小任务
left.fork();
right.fork();
//把两个小任务累加的结果合并起来
return left.join()+right.join();
} } }

执行结果:
初始化数组总和:1008
多线程执行结果:1008
分析
在Java 7中引入了一种新的线程池:ForkJoinPool。
它同ThreadPoolExecutor一样,也实现了Executor和ExecutorService接口。它使用了一个无限队列来保存需要执行的任务,而线程的数量则是通过构造函数传入,如果没有向构造函数中传入希望的线程数量,那么当前计算机可用的CPU数量会被设置为线程数量作为默认值。
ForkJoinPool主要用来使用分治法(Divide-and-Conquer Algorithm)来解决问题。典型的应用比如快速排序算法。
这里的要点在于,ForkJoinPool需要使用相对少的线程来处理大量的任务。
比如要对1000万个数据进行排序,那么会将这个任务分割成两个500万的排序任务和一个针对这两组500万数据的合并任务。以此类推,对于500万的数据也会做出同样的分割处理,到最后会设置一个阈值来规定当数据规模到多少时,停止这样的分割处理。比如,当元素的数量小于10时,会停止分割,转而使用插入排序对它们进行排序。
那么到最后,所有的任务加起来会有大概2000000+个。问题的关键在于,对于一个任务而言,只有当它所有的子任务完成之后,它才能够被执行。
所以当使用ThreadPoolExecutor时,使用分治法会存在问题,因为ThreadPoolExecutor中的线程无法像任务队列中再添加一个任务并且在等待该任务完成之后再继续执行。而使用ForkJoinPool时,就能够让其中的线程创建新的任务,并挂起当前的任务,此时线程就能够从队列中选择子任务执行。
以上程序的关键是fork()和join()方法。在ForkJoinPool使用的线程中,会使用一个内部队列来对需要执行的任务以及子任务进行操作来保证它们的执行顺序。
那么使用ThreadPoolExecutor或者ForkJoinPool,会有什么性能的差异呢?
首先,使用ForkJoinPool能够使用数量有限的线程来完成非常多的具有父子关系的任务,比如使用4个线程来完成超过200万个任务。但是,使用ThreadPoolExecutor时,是不可能完成的,因为ThreadPoolExecutor中的Thread无法选择优先执行子任务,需要完成200万个具有父子关系的任务时,也需要200万个线程,显然这是不可行的。
ps:ForkJoinPool在执行过程中,会创建大量的子任务,导致GC进行垃圾回收,这些是需要注意的。
未完待续。。。
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,让更多的人能够享受到获取知识的快乐!因为本人初入职场,鉴于自身阅历有限,所以本博客内容大部分来源于网络中已有知识的汇总,欢迎各位转载,评论,大家一起学习进步!如有侵权,请及时和我联系,切实维护您的权益!
多线程 ForkJoinPool的更多相关文章
- java多线程 -- ForkJoinPool 分支/ 合并框架 工作窃取
Fork/Join 框架:就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行 join 汇总. Fork/Join 框架与线程池的 ...
- Java 多线程中的任务分解机制-ForkJoinPool,以及CompletableFuture
ForkJoinPool的优势在于,可以充分利用多cpu,多核cpu的优势,把一个任务拆分成多个“小任务”,把多个“小任务”放到多个处理器核心上并行执行:当多个“小任务”执行完成之后,再将这些执行结果 ...
- 使用ForkJoinPool来多线程的拆分任务,执行任务,合并结果。
ForkJoinPool 是jdk1.7 由Doug Lea 写的实现 递归调用任务拆分,合并,的线程池. 代码示例: package www.itbac.com; import com.alib ...
- 0041 Java学习笔记-多线程-线程池、ForkJoinPool、ThreadLocal
什么是线程池 创建线程,因为涉及到跟操作系统交互,比较耗费资源.如果要创建大量的线程,而每个线程的生存期又很短,这时候就应该使用线程池了,就像数据库的连接池一样,预先开启一定数量的线程,有任务了就将任 ...
- java 多线程 线程池:多核CPU利用ExecutorService newWorkStealingPool; ForkJoinPool线程池 执行可拆分的任务RecursiveAction;RecursiveTask
1,给定并行级别: 1,ExecutorService newWorkStealingPool(int parallelism): 创建持有足够的线程的线程池来支持给定的并行级别,该方法还会使用多个队 ...
- java归并排序,单线程vs多线程
一.什么是归并排序 归并排序又称合并排序,它是成功应用分治技术的一个完美例子.对于一个需要排序的数组A[0..n-1],归并排序把它一分为二:A[0..n/2-1]和A[n/2..n-1],并对每个子 ...
- java 多线程--- Thread Runnable Executors
java 实现多线程的整理: Thread实现多线程的两种方式: (1)继承 Thread类,同时重载 run 方法: class PrimeThread extends Thread { long ...
- Java多线程总结(二)锁、线程池
掌握Java中的多线程,必须掌握Java中的各种锁,以及了解Java中线程池的运用.关于Java多线程基础总结可以参考我的这篇博文Java多线程总结(一)多线程基础 转载请注明出处——http://w ...
- 《Java核心技术卷一》笔记 多线程
有时,我们需要在一个程序中同时并行的处理多个任务,如播放器一边要播放音乐同时还要不断更新画面显示,或者是一边执行耗时任务,UI还能一边继续响应各种事件.还有的时候,一个任务需要很长时间才能完成,如果分 ...
随机推荐
- String拼接字符串效率低,你知道原因吗?
面试官Q1:请问为什么String用"+"拼接字符串效率低下,最好能从JVM角度谈谈吗? 对于这个问题,我们先来看看如下代码: public class StringTest { ...
- Swift Defer 延迟调用
1.Defer 在一些语言中,有 try/finally 这样的控制语句,比如 Java.这种语句可以让我们在 finally 代码块中执行必须要执行的代码,不管之前怎样的兴风作浪.在 Swift 2 ...
- Eureka微服务ID
Instance ID用于唯一标识注册到Eureka Server上的微服务实例.我们可在Eureka Server的首页直观地看到各个微服务的Instance ID.例如,图11-1中的itmuch ...
- 2.翻译系列:为EF Code-First设置开发环境(EF 6 Code-First系列)
原文链接:http://www.entityframeworktutorial.net/code-first/setup-entity-framework-code-first-environment ...
- maven 如何引入本地jar包
比如我下载了 一.怎么添加jar到本地仓库呢?步骤:1.cmd命令进入该jar包所在路径2.执行命令:mvn install:install-file -Dfile=lucene-queryparse ...
- Java知多少(75)Object类
Object 类位于 java.lang 包中,是所有 Java 类的祖先,Java 中的每个类都由它扩展而来. 定义Java类时如果没有显示的指明父类,那么就默认继承了 Object 类.例如: p ...
- Thinkphp5 关联模型
必须建立两个模型分类模型(attr).文章模型(article) attr模型 <?php namespace app\common\model; use think\Model; class ...
- sublime text plugins
Sublime Text 插件,HTML+CSS+JAVASCRIPT+JSON快速格式化: htmlpretty 快捷键:Ctrl+Shift+H Essential Sublime Text 2 ...
- Python中类的特殊变量
特殊变量 类似__xx,以双下划线开头的实例变量名,就变成了一个私有变量(private),只有内部可以访问,外部不能访问: 类似__xx__,以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变 ...
- ABBYY FineReader Pro for Mac有哪些特性(上)
使用ABBYY FineReader Pro for Mac轻松转换纸质文档.PDF文件和数字文本照片为可编辑和可搜索的文件,再也不需要手动重新输入或格式化了,相反,可以编辑.搜索.共享.归档和复制文 ...