ForkJoin的使用及for循环、stream并行流三种方式的时间比较

本文介绍了Java中ForkJoin的相关知识,它可将任务划分成小任务让多线程并行执行,使用递归算法和分治思想,核心是工作窃取。还给出了ForkJoin的使用步骤和代码示例,并比较了普通For循环、ForkJoin和Stream并行流求1~10亿总和的效率,结果是Stream流>for循环>ForkJoin。

1. ForkJoin简介

ForkJoin的作用是将一个任务划分成多个小任务,让多个线程去并行执行一个任务,在一些高并发的场景可能会用到

ForkJoin框架提供了一个ForkJoinPool (ForkJoin线程池),当任务没有指定线程的时候,就会使用默认的ForkJoin线程池

ForkJoin使用了递归算法和分治思想,Forkjoin需要在多核cpu下才会奏效,因为使用的是并行处理的方式,核心思想是工作窃取

工作窃取

当一个线程执行完任务后,去窃取其他线程的任务做
在这里插入图片描述

2. ForkJoin的使用

使用步骤

(1) 创建一个任务类继承 RecursiveTask抽象类,并重写里面的compute() 方法,在compute()中使用递归算法、分治思想

(2) 在主方法中的操作

① 创建ForkJoin线程池(new ForkJoinPool())

② 将任务提交给线程池 ForkJoinTask sumbit = forkJoinPool.summit();

③ 获取结果 (submit.get())

代码示例

(求1~10亿总和的值)

//(1) 继承RecursiveTask抽象类,重写compute()方法(使用递归算法)
public class MyForkJoinTask extends RecursiveTask<Long>{

        //记录线程开始的值
        private Long startValue;
        //记录结束的值
        private Long endValue;
        //最小拆分的值
        private Long MAX = 100_0000L;

        MyForkJoinTask(Long startValue,Long endValue){
            this.startValue = startValue;
            this.endValue = endValue;
        }

        @Override
        protected Long compute() {
            Long res=0L;
            //如果拆分的值小于max了,则开始任务
            if(endValue-startValue<MAX){
//                System.out.println("任务开始的值:"+startValue+"====任务结束的值:"+endValue);
                res = 0L;
                for (long i = startValue; i < endValue; i++) {
                    res+=i;
                }
            }
            //如果拆分的值比max大,则继续拆分
            else {
                //将前一半的任务给joinTask1
                MyForkJoinTask joinTask1 = new MyForkJoinTask(startValue,(startValue+endValue)/2);
                //将后一半任务给joinTask2
                MyForkJoinTask joinTask2 = new MyForkJoinTask((startValue+endValue)/2,endValue);
                //同时开启任务线程1和线程2
                invokeAll(joinTask1,joinTask2);
                return joinTask1.join()+joinTask2.join();
            }
            return res;
        }
        
        //2. 主方法中使用ForkJoin
    	public static void main(){
        //获取开始时间
        long start = System.currentTimeMillis();
        Long res=0L;
        //(1) 创建ForkJoin的线程池
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        //(2) 开启任务
        ForkJoinTask<Long> submit = forkJoinPool.submit(new MyForkJoinTask(1L, 10_0000_0000L));
        try {
            //(3) 获取结果
            res  = submit.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        //结束时间
        long end = System.currentTimeMillis();
        System.out.println("ForkJoin方式的结果是:"+res);
        System.out.println("花费的时间是:"+(end-start));
    }
}

3. 三种方式的效率比较

比较求1~10亿总和的运行时间

(1) 普通方式(For循环)

 public static void normal(){
        //获取开始时间
        long start = System.currentTimeMillis();
        long res=0;
        for (int i = 0; i < 10_0000_0000; i++) {
            res+=i;
        }
        //结束时间
        long end = System.currentTimeMillis();
        System.out.println("普通方式的结果是:"+res);
        System.out.println("花费的时间是:"+(end-start));
        System.out.println("==============");
    }

(2) ForkJoin

public static void forkJoin(){
        //获取开始时间
        long start = System.currentTimeMillis();
        Long res=0L;
        //1. 创建ForkJoin的线程池
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        //2. 开启任务
        ForkJoinTask<Long> submit = forkJoinPool.submit(new MyForkJoinTask(1L, 10_0000_0000L));
        try {
            //3.获取结果
            res  = submit.get();
//            System.out.println("结果是:"+res);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        //结束时间
        long end = System.currentTimeMillis();
        System.out.println("ForkJoin方式的结果是:"+res);
        System.out.println("花费的时间是:"+(end-start));
        System.out.println("========================");
    }

(3) Stream并行流

 public static void testStream(){
        Long start = System.currentTimeMillis();
        //parallel表示并行运算 Long::sum调用Long下的求和方法
        Long res = LongStream.range(0,10_0000_0000L).parallel().reduce(0,Long::sum);
        Long end= System.currentTimeMillis();
        System.out.println("stram流方式的结果是:"+res);
        System.out.println("花费的时间是:"+(end-start));
    }

在这里插入图片描述
运行效率 Stream流>for循环>ForkJoin

可能是算法不好导致ForkJoin比for循环慢,不大理解这一点

### JavaStream for 循环遍历性能对比 在 Java 编程中,`Stream` 和 `for` 循环都是常用的集合或数组遍历工具。尽管两者都能完成相同的功能,但在实际应用中的性能表现却存在显著差异。 #### 性能影响因素 1. **数据规模** 对于小型数据集(如几百到几千条记录),`for` 循环通常表现出更好的性能[^2]。这是因为 `Stream` 的内部实现涉及额外的操作开销,例如创建管道、中间操作链路以及终端操作执行等。 2. **并行能力** 当面对大规模数据集时,`Stream` 提供了内置的并行化支持(通过 `parallelStream()` 方法)。这种机制利用了 JVM 的 Fork/Join 框架来分配任务给多个线程并发执行,从而可能大幅提高性能[^4]。然而,并行化的引入也伴随着一定的初始化成本,因此对于较小的数据量来说,这种方式可能会适得其反。 3. **内存消耗** 使用 `Stream` 可能会增加应用程序的内存占用率,因为每次调用都会生成新的对象实例用于表示不同的阶段状态。而传统 `for` 循环则相对轻量化,在这方面更具优势[^1]。 4. **代码复杂度 vs 易读性** 虽然从纯技术角度考虑,简单迭代任务下 `for` 循环往往更快;但从开发效率角度看,借助 lambda 表达式的简洁语法结构使得基于 `Stream` 的解决方案更加清晰直观易维护[^3]。 #### 实验案例分析 以下是两个简单的测试例子用来说明两者的区别: ##### 示例 1 - 单线程环境下的顺序处理 假设有一个包含一百万个整数元素的大列表需要求和计算总值: ```java // For Loop Version long startTimeForLoop = System.currentTimeMillis(); int sumForLoop = 0; for (Integer num : largeList){ sumForLoop +=num; } System.out.println("Time taken by For loop:"+(System.currentTimeMillis()-startTimeForLoop)); // Stream API Sequential Version long startTimeStreamSeq = System.currentTimeMillis(); int sumStreamSeq=largeList.stream().mapToInt(Integer::intValue).sum(); System.out.println("Time taken by sequential stream:"+ (System.currentTimeMillis()-startTimeStreamSeq)); ``` 根据多次运行结果统计发现,在单核处理器或者小批量运算情况下,“for”版本一般要比对应的“stream seq.”快一些[^2]。 ##### 示例 2 - 多线程环境下采用 Parallel Streams 加速 如果允许充分利用现代计算机多核心架构的优势,则可以尝试启用 paralleled streams 来进一步提升速度: ```java // Parallel Stream Example long startTimeParallelStream = System.currentTimeMillis(); int sumParallelStream = largeList.parallelStream().mapToInt(Integer::intValue).sum(); System.out.println("Time taken by parallel stream:" + (System.currentTimeMillis() - startTimeParallelStream)); ``` 理论上讲,只要输入源足够庞大并且能够被有效分割成独立子部分交给不同 worker threads 同步工作的话,那么这种方法应当提供最佳的整体吞吐量[^4]。 综上所述,选择哪种方法取决于具体的业务需求和技术约束条件。如果是追求极致的速度又不关心其他附加价值的话,原始风格的手动控制型循环语句可能是最合适的选项之一;但如果希望获得更高的抽象层次以便快速构建原型或是简化逻辑表达的同时还能接受一定程度上的潜在损耗,则推荐优先选用功能强大的Streams库[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值