java中Fork\Join框架的使用

什么是Fork\Join框架

从JDK1.7开始,Java提供Fork/Join框架用于并行执行任务。它的思想就是讲一个大任务分割成若干小任务,最终汇总每个小任务的结果得到这个大任务的结果。如下图:
在这里插入图片描述

如何实现Fork\Join

java 的ForkJoinTask 抽象类中提供compute方法给我们实现这种思想。java又提供两个抽象类继承ForkJoinTask ,分别是:RecursiveTask有返回值,RecursiveAction无返回值。

java7代码实例

1、定义一个实体类

package com.test;

import lombok.Builder;
import lombok.Data;

import java.io.Serializable;

@Data
@Builder
public class Student implements Serializable {
    
    
    private static final long serialVersionUID = -6467381368884568257L;

    private int code;

    private String name;
}

2、编写Fork\Join框架和业务代码

package com.test;

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.RecursiveTask;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@NoArgsConstructor
@AllArgsConstructor
public class ForkJoinPoolList extends RecursiveTask<List<String>> {
    
    

    private static final long serialVersionUID = -4154344699699776282L;
    private List<Student> lists;
    private int start;
    private int end;
    private int max;

    @Override
    protected List<String> compute() {
    
    
        if((end - start) < max) {
    
    
            // 切分到可以执行状态
            System.out.println("当前线程为:" + Thread.currentThread().getName() + ";开始:" + start + ";结束:" + end);

            List<String> results = new ArrayList<>();
            for(int i = start - 1; i < end; i++) {
    
    
                results.add(lists.get(i).getCode() + "\t" + lists.get(i).getName());
            }
            return results;
        } else {
    
    
            // 任务分解
            int average = (start + end) / 2;
            ForkJoinPoolList task1 = new ForkJoinPoolList(lists, start, average, max);
            ForkJoinPoolList task2 = new ForkJoinPoolList(lists, average + 1, end, max);
            task1.fork();
            task2.fork();
            return Stream.concat(task1.join().stream(), task2.join().stream()).collect(Collectors.toList());
        }
    }
}

3、调用Fork\Join

package com.test;

import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;

public class ForkJoinTest {
    
    

    @Test
    public void testForkJoinList() throws ExecutionException, InterruptedException {
    
    
        List<Student> students = new ArrayList<>();
        for(int i = 1; i <= 1000000; i++) {
    
    
            students.add(Student.builder().code(i).name(i % 2 == 0 ? "张三" : "李四").build());
        }

        // Fork/Join框架的线程池
        ForkJoinPool pool = new ForkJoinPool();
        ForkJoinTask<List<String>> taskFuture = pool.submit(new ForkJoinPoolList(students, 1, students.size(), 1000));
        List<String> results = taskFuture.get();
    }
    
}

本例子实现了将一个大的List处理,转换成多个小list并且分发给多个线程进行操作,同时将多个小list执行的结果合并起来的过程。

java8代码实现

package com.test;

import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class ForkJoinTest {
    
    

    @Test
    public void testParallelStream() {
    
    
        List<Student> students = new ArrayList<>();
        for(int i = 1; i <= 1000000; i++) {
    
    
            students.add(Student.builder().code(i).name(i % 2 == 0 ? "张三" : "李四").build());
        }
        List<String> results = students.parallelStream().map(stu -> {
    
    
            System.out.println("当前线程为:" + Thread.currentThread().getName());
            return stu.getCode() + "\t" + stu.getName();
        }).collect(Collectors.toList());
    }

}

其中parallelStream是java8提供的并行流,内部还是通过Fork\Join框架来实现。

使用场景

虽然Fork\Join可以高效并行处理,但是如果使用不当,不但不能高效,反而会降低代码效率。
就比如上面的实例,由于业务的复杂度不够,使用框架反而会降低性能,使用如下单线程执行还会有更快的效率。

@Test
public void testList() {
    
    
    List<Student> students = new ArrayList<>();
    for(int i = 1; i <= 1000000; i++) {
    
    
        students.add(Student.builder().code(i).name(i % 2 == 0 ? "张三" : "李四").build());
    }
    List<String> results = students.stream().map(stu -> stu.getCode() + "\t" + stu.getName()).collect(Collectors.toList());
}

这是因为初始化fork/join框架的时间会远多于执行任务所需时间,也就导致了效率的降低。

所以要使用并行执行,需要满足一下条件:

  • 计算的规模要够大,确保在线程中运算的时间多过分配线程的开销
  • 并行的数据必须是无序并且无状态的,前者会影响速度,后者会影响结果正确性(状态的意思是共享的意思,如共享的对象,变量等)

根据附录doug Lee的说明,任务数量*执行方法的行数>=10000或者执行的是消耗大量时间操作(如io/数据库)才有必要使用

猜你喜欢

转载自blog.csdn.net/zhangjian8641/article/details/109031167