Java并发编程:ForkJoin框架

分支合并,分而治之。

  • Fork/Join 框架是一个实现了 ExecutorService 接口的多线程处理器,它专为那些可以通过递归分解成更细小的任务而设计,最大化的利用多核处理器来提高应用程序的性能。与其他 ExecutorService 相关的实现相同的是,Fork/Join 框架会将任务分配给线程池中的线程,而与之不同的是,Fork/Join 框架在执行任务时使用了工作窃取算法。
  • Fork/Join框架:在必要的情况下,将一个大任务,拆分成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行合并。
    • 大任务:任务递归分配成若干小任务。
    • 小任务:并行求值后结果合并。
  • Fork/Join框架与线程池的区别:
    • Fork/Join框架采用工作窃取模式(Work-Stealing)。
    • 相对于一般的线程池实现,Fork/Join框架的优势体现在对其中包含的任务的处理方式上。在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态。而在Fork/Join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行,那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行。这种方式减少了线程的等待时间,提高了性能。
  • 工作窃取算法:将一个大任务分割为若干互不依赖的子任务,为了减少线程间的竞争,把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应。但是有的线程会先把自己对应的队列里的任务干完,而其他线程对应的队列里还有任务等待处理,干完活的线程就会去其他线程的队列里窃取一个任务来执行,此时他们会访问同一个队列,为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程从双端队列的头部取出任务执行,窃取任务线程从双端队列的尾部取出任务执行。当一个线程在窃取任务时要是没有其他可用的任务了,这个线程会进入阻塞状态以等待再次“工作”。
  • 工作窃取算法的有点:充分利用线程进行并行计算,减少线程间的竞争。
  • 工作窃取算法的缺点:某些情况下还是存在线程竞争,比如双端队列里只有一个任务时。该算法会消耗更多的系统资源,比如创建多个线程和多个双端队列。
  • ForkJoinPool 由 ForkJoinTask 数组和 ForkJoinWorkerThread 数组组成,ForkJoinTask 数组负责将存放程序提交给 ForkJoinPool 的任务,ForkJoinWorkerThread 数组负责执行这些任务。
  • ForkJoinTask 是一个类似普通线程的实体,但是比普通线程轻量得多。ForkJoinWorkerThread 是执行 ForkJoinTask 的专有线程,由 ForkJoinPool 管理。
  • ForkJoinPool 是用于执行 ForkJoinTask 任务的执行(线程)池。ForkJoinPool 管理着执行池中的线程和任务队列,执行池是否还接受任务、显示线程的运行状态等也是它的功能。
  • ForkJoinPool 与传统线程池最显著的区别就是它维护了一个工作队列数组volatile WorkQueue[] workQueues,ForkJoinPool 中的每个工作线程都维护着一个工作队列。

package com.java.forkjoinpool;

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

/**
 * @author rrqstart
 * @Description 计算0~100所有数的和
 * public class ForkJoinPool extends AbstractExecutorService {//......}
 * public abstract class ForkJoinTask<V> implements Future<V>, Serializable {//......}
 * public abstract class RecursiveTask<V> extends ForkJoinTask<V> {//......}
 */
public class ForkJoinPoolTest {
    public static void main(String[] args) {
        ForkJoinPool pool = null;
        try {
            MyTask myTask = new MyTask(0, 100); //生成一个计算任务
            pool = new ForkJoinPool();

            ForkJoinTask<Integer> forkJoinTask = pool.submit(myTask); //执行一个任务
            Integer result = forkJoinTask.get();
            System.out.println("result = " + result); //result = 5050
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        } finally {
            pool.shutdown();
        }
    }
}

class MyTask extends RecursiveTask<Integer> {
    private static final Integer ADJUST_VALUE = 10; //阈值

    private int begin;
    private int end;
    private int result;

    public MyTask(int begin, int end) {
        this.begin = begin;
        this.end = end;
    }

    @Override
    protected Integer compute() {
    	//如果任务足够小,就直接计算任务
        if ((end - begin) <= ADJUST_VALUE) {
            for (int i = begin; i <= end; i++) {
                result += i;
            }
        } else {
        	//如果任务大于阈值,就分割成两个子任务
            int middle = (begin + end) / 2;
            MyTask task1 = new MyTask(begin, middle);
            MyTask task2 = new MyTask(middle + 1, end);
            //fork()方法:使用线程池中的空闲线程异步提交任务,把任务推入当前工作线程的工作队列里.
            //执行子任务:子任务调用fork方法时会再次进入compute方法
            task1.fork();
            task2.fork();
            //join()方法:等待处理任务的线程处理完毕,获得返回值.
            //Thread.join()会使线程阻塞,而ForkJoinPool.join()会使线程免于阻塞.
            //join方法会等待子任务执行完并得到其结果
            result = task1.join() + task2.join();
        }
        return result;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值