1. Fork/Join模式

线程池可以高效执行大量小任务:

Fork/Join线程池可以执行一种特殊的任务:

  • 把一个大任务拆成多个小任务并行执行
  • Fork/Join是在JDK 1.7引入的

Fork/Join模式的应用:

  • java.util.Arrays.parallelSort(array):方法内部通过Fork/Join对一个大数组进行并行排序,在多核CPU上就可以大大提高排序的速度。

例如:计算一个有1000万个元素数组的和。

如果把一个大数组拆成2个数组,就可以利用双核CPU并行计算,最后把2个结果相加就是最终的结果。



如果拆成2个数组以后,每个数组仍然很大,可以进一步拆分成4个数组,我们就可以让4核CPU并行执行。



Fork/Join就是把一个大任务不断的拆成小任务,执行并行计算的一种模式。

  1. class SumTask extends RecursiveTask<Long> {
  2. @Override
  3. protected Long compute(){
  4. //把一个大任务分拆成2个子任务
  5. SumTask subtask1 = new Sumtask(...);
  6. SumTask subtask2 = new Sumtask(...);
  7. //调用invokeAll()同时运行2个小任务,当2个任务都完成以后,invokeAll才返回
  8. invokeAll(subtask1, subtask2);
  9. //通过join()获得2个子任务的结果
  10. Long result1 = subtask1.join();
  11. Long result2 = subtask2.join();
  12. return result1 + result2; //返回结果
  13. }
  14. }

使用Fork/Join的关键在于,在compute方法内部,我们需要把一个大任务分拆成2个小任务,然后调用invokeAll()这个方法同时运行2个小任务。

当2个任务都运行结束以后,invokeAll()才会返回。接着,我们调用join()方法获得2个小任务的执行结果,最后把2个结果相加返回。

Recursive可以不断的把自身拆成小任务并行执行

2.示例

1.创建长度为1000的随机数组成的数组,并计算和作为期望值

2.使用SumTask创建一个ForkJoinTask对象。

3.通过ForkJoinPool.commonPool()方法获得一个commonPool(),然后用invoke来执行这个任务

4.求和的任务SumTask继承制RecursiveTask,返回值是Long类型,指定常量是THREHOLD。构造方法是传入一个数组、开始索引、结束索引。

5.SumTask的compute方法:我们首先判断任务是否足够的小。是,就直接进行计算,并且返回计算的结果;否,即继续拆分,这样我们就获得了subtask1,subtask2 这两个子任务。紧接着调用invokeAll()方法,同时执行这两个子任务。然后我们用join()方法获得两个子任务的结果,相加返回。

  1. import java.awt.*;
  2. import java.util.Random;
  3. import java.util.concurrent.ForkJoinPool;
  4. import java.util.concurrent.ForkJoinTask;
  5. import java.util.concurrent.RecursiveTask;
  6. class SumTask extends RecursiveTask<Long> {//求和的任务继承至RecursiveTask,返回值是Long类型
  7. //执行常量THRESHOLD,用它来分解任务
  8. static final int THRESHOLD = 250;
  9. long[] array;
  10. int start;
  11. int end;
  12. SumTask(long[] array,int start,int end){
  13. //创建SumTask时,传入一个数组,起始结束位置的索引
  14. this.array = array;
  15. this.start = start;
  16. this.end = end;
  17. }
  18. public Long compute(){
  19. //执行compute方法时,任务足够小,直接计算返回执行的结果
  20. if(end - start <= THRESHOLD){
  21. long sum = 0;
  22. for(int i=start;i<end;i++){
  23. sum += this.array[i];
  24. try{
  25. Thread.sleep(2);
  26. }catch (InterruptedException e){}
  27. }
  28. return sum;
  29. }
  30. //如果任务太大,就一分为二,拆成2个子任务
  31. int middle = (start + end) / 2;
  32. System.out.println(String.format("split %d-%d ==> %d~%d ,%d~%d",start,end,start,middle,middle,end));
  33. SumTask subtask1 = new SumTask(this.array,start,middle);
  34. SumTask subtaks2 = new SumTask(this.array,middle,end);
  35. //调用invokeAll同时执行这两个任务
  36. invokeAll(subtask1,subtaks2);
  37. //用join获取子任务的结果
  38. Long subresult1 = subtask1.join();
  39. Long subresult2 = subtaks2.join();
  40. Long result = subresult1 + subresult2;
  41. System.out.println(String.format("result = %d + %d ==> %d",subresult1,subresult2,result));
  42. return result;
  43. }
  44. }
  45. public class ForkJoinTaskSample {
  46. //对一个大数组进行求和
  47. public static void main(String[] args) throws Exception{
  48. long[] array = new long[1000]; //创建一个包含1000个元素的数组
  49. long expectedSum = 0;
  50. for(int i=0;i<array.length;i++){
  51. //1.创建数组的过程中,并计算数组的和作为期望值
  52. array[i] = random();
  53. expectedSum += array[i];
  54. }
  55. System.out.println("expectedSum: "+expectedSum);
  56. //创建一个ForkJoinTask
  57. ForkJoinTask<Long> task = new SumTask(array,0,array.length);
  58. Long startTime = System.currentTimeMillis();
  59. //通过ForkJoinPool.commonPool获得一个ForkJoinPool,用invoke方法执行这个任务
  60. Long result = ForkJoinPool.commonPool().invoke(task);
  61. Long endTime = System.currentTimeMillis();
  62. System.out.println("Fork/join sum: "+result+" in "+(endTime-startTime));
  63. }
  64. static Random random = new Random(0);
  65. static long random(){
  66. return random.nextInt(10000);
  67. }
  68. }

3. 总结:

  • Fork/Join是一种基于分治的算法:分解任务+合并结果
  • Fork/JoinPool线程池可以把一个大任务拆成小任务并行执行
  • 任务类必须继承自RecursiveTask/RecursiveAction。

    * RecursiveTask有返回值;RecursiveAction没有返回值
  • 使用Fork/Join模式可以进行并行计算提高效率

廖雪峰Java11多线程编程-3高级concurrent包-9Fork_Join的更多相关文章

  1. 廖雪峰Java11多线程编程-3高级concurrent包-5Atomic

    Atomic java.util.concurrent.atomic提供了一组原子类型操作: 如AtomicInteger提供了 int addAndGet(int delta) int increm ...

  2. 廖雪峰Java11多线程编程-3高级concurrent包-4Concurrent集合

    Concurrent 用ReentrantLock+Condition实现Blocking Queue. Blocking Queue:当一个线程调用getTask()时,该方法内部可能让给线程进入等 ...

  3. 廖雪峰Java11多线程编程-3高级concurrent包-1ReentrantLock

    线程同步: 是因为多线程读写竞争资源需要同步 Java语言提供了synchronized/wait/notify来实现同步 编写多线程同步很困难 所以Java提供了java.util.concurre ...

  4. 廖雪峰Java11多线程编程-3高级concurrent包-6ExecutorService

    Java语言内置多线程支持: 创建线程需要操作系统资源(线程资源,栈空间) 频繁创建和销毁线程需要消耗大量时间 如果可以复用一个线程 线程池: 线程池维护若干个线程,处于等待状态 如果有新任务,就分配 ...

  5. 廖雪峰Java11多线程编程-3高级concurrent包-8CompletableFuture

    使用Future可以获得异步执行结果 Future<String> future = executor.submit(task); String result = future.get() ...

  6. 廖雪峰Java11多线程编程-3高级concurrent包-7Future

    JDK提供了ExecutorService接口表示线程池,可以通过submit提交一个任务. ExecutorService executor = Executors.newFixedThreadPo ...

  7. 廖雪峰Java11多线程编程-3高级concurrent包-3Condition

    Condition实现等待和唤醒线程 java.util.locks.ReentrantLock用于替代synchronized加锁 synchronized可以使用wait和notify实现在条件不 ...

  8. 廖雪峰Java11多线程编程-3高级concurrent包-2ReadWriteLock

    ReentrantLock保证单一线程执行 ReentrantLock保证了只有一个线程可以执行临界区代码: 临界区代码:任何时候只有1个线程可以执行的代码块. 临界区指的是一个访问共用资源(例如:共 ...

  9. 廖雪峰Java11多线程编程-2线程同步-3死锁

    1.线程锁可以嵌套 在多线程编程中,要执行synchronized块: 必须首先获得指定对象的锁 Java的线程锁是可重入的锁.对同一个对象,同一个线程,可以多次获取他的锁,即同一把锁可以嵌套.如以下 ...

随机推荐

  1. ionic-CSS:ionic select

    ylbtech-ionic-CSS:ionic select 1.返回顶部 1. ionic select ionic select 的 select 相比原生的要更加美观些.但是弹出的可选选项样式是 ...

  2. Spring随笔-bean装配

    Spring提供了三种装配方式 1.XML文件进行显式装配 2.java中进行显示装配 3.自动化装配 1.自动化装配的两种实现方式 1.组件扫描:Spring会自动发现应用上下文中创建的bean 2 ...

  3. 简介Python正则表达式

    一.概念 简单来说正则表达式是由一些普通字符(例如,a 到 z 之间的字母)和一些元字符组成,用来匹配和过滤一些字符串的一种逻辑公式. 二.正则表达式的一些基本规则 1.一些常用的元字符   ^  : ...

  4. Invalid Rom

    问题描述 原因说明 这多半是因为单片机超频被锁,就是HSE_VALUE设置的与实际值不一致,容易造成这个问题. 解放方法 1. 先正确配置 HSE_VLAUE, 看HSE_VALUE 修改问题. 2. ...

  5. ELK日志分析系统(原创)

    一.简介 ELK由Elasticsearch.Logstash和Kibana三部分组件组成: Elasticsearch是个开源分布式搜索引擎,它的特点有:分布式,零配置,自动发现,索引自动分片,索引 ...

  6. 当对象转换成JSON的时候处理时间格式

    /// <summary> /// 格式化日期 /// </summary> /// <param name="foramt">格式化规则< ...

  7. ci用户登录

    [list] 预先加载数据库操作类和Session类 即在autoload.php中,$autoload['libraries'] = array('database', 'session'); a. ...

  8. 如何设置树莓派 VNC 的分辨率

    当我们使用 VNC 连接到树莓派时,默认的分辨率非常低.甚至无法显示整个桌面,因此我们需要对分辨率进行设置.在树莓派上设置 VNC 的分辨率很简单,在终端运行下面指令进入设置界面设置. 1 sudo ...

  9. [JZOJ1900] 【2010集训队出题】矩阵

    题目 题目大意 题目化简一下,就变成: 构造一个\(01\)数列\(A\),使得\(D=\sum A_iA_jB_{i,j}-\sum A_iC_i\)最大. 问这个最大的\(D\)是多少. 正解 其 ...

  10. Redis 常用的数据结构

    String 字符串 set get 使用场景: 可以用来作为缓存使用(缓存更新策略和缓存雪崩如何处理) List lpop rpop lpush rpush 使用场景: set 无序集合 使用场景: ...