Java 中 Fork/Join 框架和 ExecutorService 的区别


在 Java 的并发编程领域,开发者拥有众多选择。Fork/Join 框架和 ExecutorService 是其中两种备受欢迎的方案。尽管这两种解决方案都能很好地实现操作并行化,但它们在针对不同用例需求的结构方面有所不同。通过本文对每个框架的语法特性以及实际代码示例的深入分析,用户可以更好地理解它们之间的区别,以及各自的优势。

语法

Fork/Join 框架

class ForkJoinTask<V> extends Object

ExecutorService

interface ExecutorService extends Executor

语法解释

Fork/Join 框架围绕 ForkJoinTask 类构建,该类表示可以分解成更小子任务的任务。参与该程序将为您提供学习任务递归分解以及如何并发执行它们的机会。此外,通过使用建立在 Executor 接口之上的 ExecutorService 接口,您将能够以更优越的方式执行异步任务执行。它管理一个线程池并处理任务的提交和执行。

方法 1:Fork/Join 框架

算法

  • 定义一个表示要执行的任务的 ForkJoinTask 子类。

  • 在子类中实现 compute() 方法,将任务分解成更小的子任务并调用其执行。

  • 组合来自子任务的结果以生成最终结果。

方法 1 的完整可执行代码

示例

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;

class MyTask extends RecursiveTask {
   private static final int THRESHOLD = 10;

   private int[] array;
   private int start;
   private int end;

   public MyTask(int[] array, int start, int end) {
      this.array = array;
      this.start = start;
      this.end = end;
   }

   @Override
   protected Integer compute() {
      if (end - start <= THRESHOLD) {
         // Perform the computation directly
         int sum = 0;
         for (int i = start; i < end; i++) {
            sum += array[i];
         }
         return sum;
      } else {
         // Divide the task into smaller subtasks
         int mid = start + (end - start) / 2;
         MyTask leftTask = new MyTask(array, start, mid);
         MyTask rightTask = new MyTask(array, mid, end);

         // Fork the subtasks
         leftTask.fork();
         rightTask.fork();

         // Combine the results
         int leftResult = leftTask.join();
         int rightResult = rightTask.join();

         // Return the final result
         return leftResult + rightResult;
      }
   }
}

public class ForkJoinExample {
   public static void main(String[] args) {
      int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

      MyTask task = new MyTask(array, 0, array.length);

      // Create a ForkJoinPool and invoke the task
      ForkJoinPool pool = new ForkJoinPool();
      int result = pool.invoke(task);

      System.out.println("Result: " + result);
   }
}

输出

Result: 55

方法 1 中代码的解释

我们的方法包括创建一个名为 MyTask 的专用类别,它派生自 RecursiveTask<Integer>,以便我们可以运行计算并接收输出。为了实现此目标,我们修改 compute() 方法,以便在任务超过我们的设置限制时将其划分为较小的任务。然后,较小的子任务会被分叉,并且它们的结果会被组合在一起以产生最终结果。

方法 2:ExecutorService

算法

  • 使用 Executors 类创建一个 ExecutorService 实例。

  • 定义一个表示要执行的任务的 Callable 或 Runnable 实现。

  • 将任务提交到 ExecutorService 以执行。

  • 如果需要,获取结果。

方法 2 的完整可执行代码

示例

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

class MyTask implements Callable<Integer> {
   private int[] array;
   private int start;
   private int end;

   public MyTask(int[] array, int start, int end) {
      this.array = array;
      this.start = start;
      this.end = end;
   }

   @Override
   public Integer call() throws Exception {
      int sum = 0;
      for (int i = start; i < end; i++) {
         sum += array[i];
      }
      return sum;
   }
}

public class ExecutorServiceExample {
   public static void main(String[] args) throws Exception {
      int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

      ExecutorService executorService = Executors.newFixedThreadPool(2);

      MyTask task1 = new MyTask(array, 0, 5);
      MyTask task2 = new MyTask(array, 5, 10);
      
      Future<Integer> future1 = executorService.submit(task1);
      Future<Integer> future2 = executorService.submit(task2);

      int result = future1.get() + future2.get();

      executorService.shutdown();

      System.out.println("Result: " + result);
   }
}

输出

Result: 55

方法 2 中代码的解释

在这种方法中,我们使用 Executors 类创建一个 ExecutorService,该类提供了一个具有两个线程的固定线程池。我们定义了一个 MyTask 类,它实现了 Callable接口,表示要执行的任务。call() 方法执行计算并返回结果。我们使用 submit() 方法将 MyTask 的两个实例提交到 ExecutorService,该方法返回一个表示计算结果的 Future 对象。最后,我们从 Future 对象中获取结果并计算最终结果。

方面

Fork/Join 框架

ExecutorService

语法

class ForkJoinTask<V> extends Object

interface ExecutorService extends Executor

设计目的

任务的递归分解

异步任务执行和线程管理

粒度

最适合细粒度任务

适用于细粒度和粗粒度任务

任务依赖性

隐式处理递归任务分解

需要显式提交任务

并行性

利用工作窃取算法进行负载均衡

管理线程池和任务执行

结果收集

结果以分层方式合并

结果通过 Future 对象获取

任务提交

任务内的递归分解

独立提交任务

控制

对线程管理的控制有限

对线程管理和执行有更大的控制权

用例

分治算法、递归任务

独立任务的并发执行

结论

总之,Fork/Join 框架和 ExecutorService 都为 Java 中的并发编程提供了强大的机制。Fork/Join 框架专为任务的递归分解而设计,特别适用于可以分解成子任务的问题。它允许通过利用多个线程并合并其结果来实现高效的并行执行。另一方面,ExecutorService 提供了一种更通用的异步任务执行方法,提供线程管理和对执行环境的控制。它非常适合并发执行独立任务并在需要时获取其结果。通过理解这些框架的差异和特性,开发人员可以根据其特定需求选择最合适的方法,从而创建高效且可扩展的并发 Java 程序。

更新于: 2023-07-28

392 次浏览

启动你的 职业生涯

通过完成课程获得认证

开始学习
广告

© . All rights reserved.