【JAVA并发编程专题】Fork/Join框架的理解和使用

简介: 【JAVA并发编程专题】Fork/Join框架的理解和使用

正文


一、Fork/Join简介


简单的说,Fork/Join是一个并行任务执行框架,能够把一个大的任务拆分成若干个小任务,并行地进行执行,最终还可以汇总各个小任务的执行结果。比如我们想计算1+2+…+100的结果,我们可以把这个大的任务拆分为10个小的任务,这10个小任务分别是1+…+10、11+…+20、…91+…+100,然后最终把这10个小任务的结果再加起来得到大任务的结果。


工作窃取算法,是指大任务被分成多个小任务的时候,这些小任务会被分到不通的任务队列中,每个任务队列会有一个工作线程来执行任务。但是在有些时候,有的线程会执行的比较快,提前完成了所有任务的执行,那么它就会去别的线程的任务队列中窃取任务进行执行,从而加快整体任务的完成。


但是窃取线程如何保证不和被窃取任务的线程冲突呢?这里用到了双端队列,即任务队列都是这种数据结构,队列绑定的工作线程都从队列头部取任务进行执行,而窃取线程会从别的队列尾部获取任务进行执行。


工作窃取算法充分利用多线程进行并行计算,提高了执行效率,同时使用双端队列减少了线程间的冲突竞争;然后,不能完全避免冲突,比如某个任务队列中仅有一个任务的时候,两个线程同时竞争。还有,该算法会创建多个线程和多个双端队列,对系统资源的消耗会增加。


二、Fork/Join使用


在使用之前,我们需要记住两个概念:


ForkJoinTask,我们需要自己定义一个ForkJoinTask,用来定义我们需要执行的任务,它可以继承RecursiveAction或者RecursiveTask,从而获取fork和join操作的能力,前者表示不需要返回值,后者则是需要返回值,比如我们上面的1+2+…+100的累加操作则是需要返回值的,所以应该继承RecursiveTask

ForkJoinPool,类似于线程池,连接池的概念,ForkJoinTask都需要交给ForkJoinPool才能执行。

@Slf4j
public class CountTask extends RecursiveTask<Integer> {
    // 设置最小子任务的阈值
    private static final int taskLimit = 10;
    private int start;
    private int end;
    public CountTask(int start,int end){
        this.start = start;
        this.end = end;
    }
    @Override
    protected Integer compute() {
        int sumResult = 0;
        boolean taskFlag = (end - start) <= taskLimit;
        if(taskFlag){
            log.info("进行计算,当前start为{},end为{}",start,end);
            // 子任务足够小了,可以开始执行
            for (int i=start;i<=end;i++){
                sumResult += i;
            }
        } else {
            log.info("需要拆分:当前start为{},end为{}",start,end);
            // 子任务还不是足够小,需要进一步分割
            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();
            sumResult = leftResult + rightResult;
        }
        log.info("当前计算结果为:{}",sumResult);
        return sumResult;
    }
}

我们在compute中定义任务的拆分粒度和最小任务的执行逻辑,并通过fork和join能力来实现多线程并发。当子任务调用fork的时候,会继续执行子任务的compute;当子任务调用join的时候,会等待其所有子孙任务的执行结果。

public static void main(String[] args) throws ExecutionException, InterruptedException {
    ForkJoinPool forkJoinPool = new ForkJoinPool();
    // 计算从1累加到100,应该获取5050
    CountTask myTask = new CountTask(1,100);
    ForkJoinTask<Integer> result = forkJoinPool.submit(myTask);
    if(myTask.isCompletedAbnormally()){
        log.warn("发生了异常,{}", myTask.getException());
    }
    log.info("1+2+...+100={}",result.get());
}


相关文章
|
2天前
|
设计模式 安全 Java
Java编程中的单例模式:理解与实践
【10月更文挑战第31天】在Java的世界里,单例模式是一种优雅的解决方案,它确保一个类只有一个实例,并提供一个全局访问点。本文将深入探讨单例模式的实现方式、使用场景及其优缺点,同时提供代码示例以加深理解。无论你是Java新手还是有经验的开发者,掌握单例模式都将是你技能库中的宝贵财富。
|
3天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
4天前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
19 4
|
4天前
|
消息中间件 供应链 Java
掌握Java多线程编程的艺术
【10月更文挑战第29天】 在当今软件开发领域,多线程编程已成为提升应用性能和响应速度的关键手段之一。本文旨在深入探讨Java多线程编程的核心技术、常见问题以及最佳实践,通过实际案例分析,帮助读者理解并掌握如何在Java应用中高效地使用多线程。不同于常规的技术总结,本文将结合作者多年的实践经验,以故事化的方式讲述多线程编程的魅力与挑战,旨在为读者提供一种全新的学习视角。
24 3
|
2天前
|
设计模式 安全 Java
Java编程中的单例模式深入解析
【10月更文挑战第31天】在编程世界中,设计模式就像是建筑中的蓝图,它们定义了解决常见问题的最佳实践。本文将通过浅显易懂的语言带你深入了解Java中广泛应用的单例模式,并展示如何实现它。
|
4天前
|
存储 缓存 安全
Java内存模型(JMM):深入理解并发编程的基石####
【10月更文挑战第29天】 本文作为一篇技术性文章,旨在深入探讨Java内存模型(JMM)的核心概念、工作原理及其在并发编程中的应用。我们将从JMM的基本定义出发,逐步剖析其如何通过happens-before原则、volatile关键字、synchronized关键字等机制,解决多线程环境下的数据可见性、原子性和有序性问题。不同于常规摘要的简述方式,本摘要将直接概述文章的核心内容,为读者提供一个清晰的学习路径。 ####
16 2
|
5月前
|
Java C++
关于《Java并发编程之线程池十八问》的补充内容
【6月更文挑战第6天】关于《Java并发编程之线程池十八问》的补充内容
49 5
|
2月前
|
缓存 监控 Java
Java中的并发编程:理解并应用线程池
在Java的并发编程中,线程池是提高应用程序性能的关键工具。本文将深入探讨如何有效利用线程池来管理资源、提升效率和简化代码结构。我们将从基础概念出发,逐步介绍线程池的配置、使用场景以及最佳实践,帮助开发者更好地掌握并发编程的核心技巧。
|
4月前
|
安全 Java 开发者
Java中的并发编程:深入理解线程池
在Java的并发编程中,线程池是管理资源和任务执行的核心。本文将揭示线程池的内部机制,探讨如何高效利用这一工具来优化程序的性能与响应速度。通过具体案例分析,我们将学习如何根据不同的应用场景选择合适的线程池类型及其参数配置,以及如何避免常见的并发陷阱。
55 1
|
4月前
|
监控 Java
Java并发编程:深入理解线程池
在Java并发编程领域,线程池是提升应用性能和资源管理效率的关键工具。本文将深入探讨线程池的工作原理、核心参数配置以及使用场景,通过具体案例展示如何有效利用线程池优化多线程应用的性能。