阅读目录

背景: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的更多相关文章

  1. java多线程 -- ForkJoinPool 分支/ 合并框架 工作窃取

    Fork/Join 框架:就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行 join 汇总. Fork/Join 框架与线程池的 ...

  2. Java 多线程中的任务分解机制-ForkJoinPool,以及CompletableFuture

    ForkJoinPool的优势在于,可以充分利用多cpu,多核cpu的优势,把一个任务拆分成多个“小任务”,把多个“小任务”放到多个处理器核心上并行执行:当多个“小任务”执行完成之后,再将这些执行结果 ...

  3. 使用ForkJoinPool来多线程的拆分任务,执行任务,合并结果。

    ForkJoinPool 是jdk1.7 由Doug Lea 写的实现   递归调用任务拆分,合并,的线程池. 代码示例: package www.itbac.com; import com.alib ...

  4. 0041 Java学习笔记-多线程-线程池、ForkJoinPool、ThreadLocal

    什么是线程池 创建线程,因为涉及到跟操作系统交互,比较耗费资源.如果要创建大量的线程,而每个线程的生存期又很短,这时候就应该使用线程池了,就像数据库的连接池一样,预先开启一定数量的线程,有任务了就将任 ...

  5. java 多线程 线程池:多核CPU利用ExecutorService newWorkStealingPool; ForkJoinPool线程池 执行可拆分的任务RecursiveAction;RecursiveTask

    1,给定并行级别: 1,ExecutorService newWorkStealingPool(int parallelism): 创建持有足够的线程的线程池来支持给定的并行级别,该方法还会使用多个队 ...

  6. java归并排序,单线程vs多线程

    一.什么是归并排序 归并排序又称合并排序,它是成功应用分治技术的一个完美例子.对于一个需要排序的数组A[0..n-1],归并排序把它一分为二:A[0..n/2-1]和A[n/2..n-1],并对每个子 ...

  7. java 多线程--- Thread Runnable Executors

    java 实现多线程的整理: Thread实现多线程的两种方式: (1)继承 Thread类,同时重载 run 方法: class PrimeThread extends Thread { long ...

  8. Java多线程总结(二)锁、线程池

    掌握Java中的多线程,必须掌握Java中的各种锁,以及了解Java中线程池的运用.关于Java多线程基础总结可以参考我的这篇博文Java多线程总结(一)多线程基础 转载请注明出处——http://w ...

  9. 《Java核心技术卷一》笔记 多线程

    有时,我们需要在一个程序中同时并行的处理多个任务,如播放器一边要播放音乐同时还要不断更新画面显示,或者是一边执行耗时任务,UI还能一边继续响应各种事件.还有的时候,一个任务需要很长时间才能完成,如果分 ...

随机推荐

  1. nginx做负载均衡时其中一台服务器挂掉宕机时响应速度慢的问题解决

    nginx会根据预先设置的权重转发请求, 若给某一台服务器转发请求时,达到默认超时时间未响应,则再向另一台服务器转发请求. 默认超时时间1分钟. 修改默认超时时间为1s: server { liste ...

  2. 第三部分:Android 应用程序接口指南---第二节:UI---第九章 搜索

    第9章 搜索 在android平台上搜索是一个核心的用户功能.无论内容位于设备或网络上,用户应该能够搜索任何对它们可用的数据.为了创建一个一致的用户搜索体验,Android平台提供了一个搜索框架帮助你 ...

  3. Atitit 提升进度的大原则与方法  高层方法  attilax总结

    Atitit 提升进度的大原则与方法  高层方法  attilax总结 生产力的提升点 1.1. 管理,管理的发展发展非常缓慢,1 1.2. 方法论(前后分离,dsl等)1 1.3. 工具( 工具链 ...

  4. 解决Redis之MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist o...

    解决Redis之MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist o... ...

  5. Android开发(二十三)——Application

    参考: [1] Android中Application类用法.http://www.cnblogs.com/renqingping/archive/2012/10/24/Application.htm ...

  6. 【九天教您南方cass 9.1】 10 DTM土方计算的四种方法

    同学们大家好,欢迎收看由老王测量上班记出品的cass9.1视频课程 我是本节课主讲老师九天. 我们讲课的教程附件也是共享的,请注意索取测量空间中. [点击索取cass教程]5元立得 (给客服说暗号:“ ...

  7. NodeJS的url验证库模块url-valid

    这是我10月份做的项目其中的一个部件,主要用于url检验的. 我们知道Javascript做url检验,通常是使用正则表达式来判定,其格式是否正确,例如: /^https?:\/\//.test(ur ...

  8. BitTorrent Sync 基于BT的文件同步

    BitTorrent Sync 神奇的文件同步软件,无需服务器让多台电脑间无限制互相同步!   BitTorrent Labs 推出的实验性项目,可以在多台电脑间进行点对点自动分享/同步文件,由于没有 ...

  9. wpf 模板选择器DataTemplateSelector及动态绑定,DataTemplate.Triggers触发器的使用

    通常,如果有多个 DataTemplate 可用于同一类型的对象,并且您希望根据每个数据对象的属性提供自己的逻辑来选择要应用的 DataTemplate,则应创建 DataTemplateSelect ...

  10. python初级 1 数据类型和变量

    一.整数(int) 例:  0 1 2 3 -1 -2 –3 In [31]: print(type(0)) <class 'int'> In [32]: print(type(1)) & ...