当前位置 : 主页 > 编程语言 > java >

Java并发编程之Fork-Join概念 *

来源:互联网 收集:自由互联 发布时间:2022-07-05
前言 java下多线程的开发可以我们自己启用多线程,线程池,还可以使用forkjoin,forkjoin可以让我们不去了解诸如Thread,Runnable等相关的知识,只要遵循forkjoin的开发模式,就可以写出很好

前言

java下多线程的开发可以我们自己启用多线程,线程池,还可以使用forkjoin,forkjoin可以让我们不去了解诸如Thread,Runnable等相关的知识,只要遵循forkjoin的开发模式,就可以写出很好的多线程并发程序.

概念

分而治之

分治法的设计思想是:将一个难以直接解决的大问题,分割成一些规模较小的相同问题(小问题之间无关联),以便各个击破,分而治之。
分治策略是:对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同(子问题相互之间有联系就会变为动态规范算法),递归地解这些子问题,然后将各子问题的解合并得到原问题的解。这种算法设计策略叫做分治法。

归并排序

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。
若将两个有序表合并成一个有序表,称为2-路归并,与之对应的还有多路归并。
对于给定的一组数据,利用递归与分治技术将数据序列划分成为越来越小的半子表,在对半子表排序后,再用递归方法将排好序的半子表合并成为越来越大的有序序列。
为了提升性能,有时我们在半子表的个数小于某个数(比如15)的情况下,对半子表的排序采用其他排序算法,比如插入排序。

归并排序(降序)示例

Java并发编程之Fork-Join概念 *_forkjoin
先将数组划分为左右两个子表:
Java并发编程之Fork-Join概念 *_子任务_02
然后继续左右两个子表拆分:
Java并发编程之Fork-Join概念 *_双端队列_03
对最后的拆分的子表,两两进行排序
Java并发编程之Fork-Join概念 *_归并排序_04
对有序的子表进行排序和比较合并Java并发编程之Fork-Join概念 *_forkjoin_05
对合并后的子表继续比较合并
Java并发编程之Fork-Join概念 *_子任务_06

Fork-Join原理

说白了就是把大任务拆分.每个小任务单独处理(交给线程去执行),处理完之后再一级一级的汇总,最后汇总成最终的结果
Java并发编程之Fork-Join概念 *_子任务_07

工作密取算法

即当前线程的Task已经全被执行完毕,则自动取到其他线程的Task池中取出Task继续执行。
ForkJoinPool中维护着多个线程(一般为CPU核数)在不断地执行Task,每个线程除了执行自己职务内的Task之外,还会根据自己工作线程的闲置情况去获取其他繁忙的工作线程的Task,如此一来就能能够减少线程阻塞或是闲置的时间,提高CPU利用率。

工作窃取(work-stealing)算法是指某个线程从其他队列里窃取任务来执行。那么,为什么需要使用工作窃取算法呢?
假如我们需要做一个比较大的任务,可以把这个任务分割为若干互不依赖的子任务,为了减少线程间的竞争,把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应。比如A线程负责处理A队列里的任务。但是,有的线程会先把自己队列里的任务干完,而其他线程对应的队列里还有任务等待处理。干完活的线程与其等着,不如去帮其他线程干活,于是它就去其他线程的队列里窃取一个任务来执行。而在这时它们会访问同一个队列,所以为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行。
Java并发编程之Fork-Join概念 *_java_08

工作密取优缺点

工作窃取算法的优点:充分利用线程进行并行计算,减少了线程间的竞争。
工作窃取算法的缺点:在某些情况下还是存在竞争,比如双端队列里只有一个任务时。并且该算法会消耗了更多的系统资源,比如创建多个线程和多个双端队列。

使用场景

Excel表的统计
sql统计(统计100万条记录,单线程一条一条的统计,然后发现很慢,后来拆分一万条一组去统计,效率就高了很多倍.)

还需要考虑线程上下文切换的时间问题 ,如果数据量少(几千个之内的)的话可能还不如单线程统计来的快. 但是数据量大的话,或者处理比较耗时,Fork-Join优势就起来了.

Fork/Join使用方式

我们要使用ForkJoin框架,必须首先创建一个ForkJoin任务。它提供在任务中执行fork和join的操作机制
通常我们不直接继承ForkjoinTask类,只需要直接继承其子类。

  • RecursiveAction,用于没有返回结果的任务
  • RecursiveTask,用于有返回值的任务
  • task要通过ForkJoinPool来执行,使用submit 或 invoke 提交,两者的区别是:
    1.invoke是同步执行,调用之后需要等待任务完成,才能执行后面的代码;
    2.submit是异步执行。调用之后可以继续执行下面代码,不用等任务完成.

    join()和get方法当任务完成的时候返回计算结果。
    Java并发编程之Fork-Join概念 *_归并排序_09
    在我们自己实现的compute方法里,首先需要判断任务是否足够小,如果足够小就直接执行任务。如果不足够小,就必须分割成两个子任务,每个子任务在调用invokeAll方法时,又会进入compute方法,看看当前子任务是否需要继续分割成孙任务,如果不需要继续分割,则执行当前子任务并返回结果。使用join方法会等待子任务执行完并得到其结果。


    上一篇:Java多线程之wait()和notify()和notifyAll() *
    下一篇:没有了
    网友评论