2021-03-27

Fork/Join 框架


本文部分摘自《Java 并发编程的艺术》


Fork/Join 框架概述

Fork/Join 框架是 Java7 提供的一个用于并行执行任务的框架,是把一个大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架,其运行流程如图所示:


工作窃取算法

工作窃取算法是指某个线程从其他队列里窃取任务来执行,为什么要这样做呢?假如我们需要做一个比较大的任务,可以把这个任务分割为若干个互不依赖的子任务,为了减少线程间的竞争,把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应。然而,如果某一线程先把自己队列的任务干完了,而其他线程对应的队列里还有任务等待处理,干完活的线程与其等着,不如去帮其他线程干活,这就是工作窃取算法的动机。

为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行


使用 Fork/Join 框架

首先思考一下,如果让我们来设计一个 Fork/Join 框架,该如何设计呢?

  1. 分割任务

    首先我们需要一个有 fork 类来把大任务分割成子任务,有可能子任务还是很大,所以需要不停地分割,直到分割出来的子任务足够小

  2. 执行任务并合并结果

    分割的子任务分别放在双端队列里,然后几个启动线程分别从双端队列里获取任务执行。子任务执行完的结果都统一放在一个队列里,启动一个线程从队列里拿数据,然后进行合并

Fork/Join 使用两个类来完成以上两件事情:

  1. ForkJoinTask

    我们使用 ForkJoin 框架,必须首先创建 ForkJoin 任务,它提供在任务中执行 fork() 和 join() 操作的机制。通常情况下,我们不需要直接继承 ForkJoinTask 类,只需要继承它的子类即可,Fork/Join 框架提供了以下两个子类:

    • RecursiveAction:用于没有返回结果的任务
    • RecursiveTask:用于有返回结果待任务
  2. ForkJoinPool

    ForkJoinTask 需要通过 ForkJoinPool 来执行

我们通过一个简单的需求来使用 Fork/Join 框架,需求是:计算 1+2+3+4 的结果

使用 Fork/Join 框架把这个任务 fork 成两个子任务,子任务一负责计算 1+2,子任务而负责计算 3+4,然后再 join 两个子任务的结果,因为是有结果的任务,所以必须继承 RecursiveTask,代码实现如下:

public class CountTask extends RecursiveTask<Integer> { // 阈值 private static final int THRESHOLD = 2; private final int start; private final int end; public CountTask(int start, int end) {  this.start = start;  this.end = end; } @Override protected Integer compute() {  int sum = 0;  boolean canCompute = (end - start) <= THRESHOLD;  // 如果任务足够小就计算任务  if (canCompute) {   for (int i = start; i <= end; i++) {    sum += i;   }  } else {   // 如果任务大于阈值,就分裂成两个子任务计算   int middle = (start + end) / 2;   CountTask leftTask = new CountTask(start, middle);   CountTask rightTask = new CountTask(middle + 1, end);   // 执行子任务   leftTask.fork();   rightTask.fork();   // 等待子任务执行完,并得到其结果   int leftResult = leftTask.join();   int rightResult = rightTask.join();   // 合并子任务   sum = leftResult + rightResult;  }  return sum; } public static void main(String[] args) {  ForkJoinPool forkJoinPool = new ForkJoinPool();  // 生成一个计算任务,负责计算 1+2+3+4  CountTask task = new CountTask(1, 4);  // 执行一个任务  Future<Integer> result = forkJoinPool.submit(task);  try {   System.out.println(result.get());  } catch (InterruptedException | ExecutionException e) {   e.printStackTrace();  } }}

上面的例子中是通过 new ForkJoinPool(),然而这并不是其作者 Doug Lea 推荐的方式。ForkJoinPool 类有一个静态方法commonPool(),它所获得的 ForkJoinPool 实例是由整个应用进程共享的,可以帮助应用程序中多个需要进行归并计算的任务共享计算资源

ForkJoinPool forkJoinPool = ForkJoinPool.commonPool();

ForkJoinTask 在执行的时候可能会抛出异常,但我们没办法在主线程直接捕获线程,所以 ForkJoinTask 提供了 isCompletedAbnormally() 方法来检查任务是否已经抛出异常或已经被取消,并可以通过 ForkJoinTask 的 getException 方法获取异常

if(task.isCompletedAbnormally()) { System.out.println(task.getException());}

Fork/Join 框架的实现原理

ForkJoinPool 中用来处理任务的工作线程采用的是 ForkJoinWorkerThread,它继承了 Thread 类,拥有两个非常关键的变量

final ForkJoinPool pool;final ForkJoinPool.WorkQueue workQueue;

pool 是这个工作线程所属的 ForkJoinPool 实例,workQueue 是一个双端队列,可以发现,它是 ForkJoinPool 的一个内部类,其结构如下(省略部分代码)

static final class WorkQueue {	...  ForkJoinTask<?>[] array; final ForkJoinPool pool; final ForkJoinWorkerThread owner;	...}

WorkQueue 里维护一个 ForkJoinTask 数组,用来存放待执行的任务(ForkJoinTask)。所以 Fork/Join 框架的基本思想就是:ForkJoinPool 的每个工作线程都维护着一个工作队列(WorkQueue),里面存放的对象是任务,每个工作线程处理自己的工作队列里的任务

fork() 方法做的工作只有一件事,既是把任务推入当前工作线程的工作队列里

public final ForkJoinTask<V> fork() { Thread t; if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)  ((ForkJoinWorkerThread)t).workQueue.push(this); else  ForkJoinPool.common.externalPush(this); return this;}

join() 方法的工作则复杂一些,首先会判断线程是否为 ForkJoinThread 线程,如果不是,阻塞当前线程,等待任务完成,如果是,则不阻塞。接着查看任务的完成状态,如果已经完成,直接返回结果,否则从队列中取出任务执行










原文转载:http://www.shaoqun.com/a/645729.html

跨境电商:https://www.ikjzd.com/

米谷:https://www.ikjzd.com/w/1788

联动优势电子商务:https://www.ikjzd.com/w/1921


本文部分摘自《Java并发编程的艺术》Fork/Join框架概述Fork/Join框架是Java7提供的一个用于并行执行任务的框架,是把一个大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架,其运行流程如图所示:工作窃取算法工作窃取算法是指某个线程从其他队列里窃取任务来执行,为什么要这样做呢?假如我们需要做一个比较大的任务,可以把这个任务分割为若干个互不依赖的子任务,为了减少线
网易考拉海购大促:https://www.ikjzd.com/w/1052
anker:https://www.ikjzd.com/w/1027
livingsocial:https://www.ikjzd.com/w/714.html
2018圣诞节大促前夕,亚马逊上线两个新功能!:https://www.ikjzd.com/home/12041
FBA这样用,让物流费减半,销量飙升!:https://www.ikjzd.com/home/102031
产品还在海上漂! 亚马逊后台早已在售?:https://www.ikjzd.com/home/142364

No comments:

Post a Comment