ForkJoinTask 使用后记载

本文深入探讨Java 7引入的Fork/Join框架,一种高效处理并行任务的机制。通过实例演示如何将大任务分解为小任务并行处理,利用ForkJoinTask、ForkJoinPool实现多线程编程,提高程序执行效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JAVA ForkJoinTask 使用后记载

多线程这个东西并不陌生,可惜一直没有在实际中写过,最近突然要写一个多线程的功能,做完后感觉这个东西挺有意思的,后面对ForkJoinTask做了一些了解。

简单介绍

Fork/Join框架是Java 7提供的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。Fork/Join框架要完成两件事情:
  1.任务分割:首先Fork/Join框架需要把大的任务分割成足够小的子任务,如果子任务比较大的话还要对子任务进行继续分割
  2.执行任务并合并结果:分割的子任务分别放到双端队列里,然后几个启动线程分别从双端队列里获取任务执行。子任务执行完的结果都放在另外一个队列里,启动一个线程从队列里取数据,然后合并这些数据。

要使用ForkJoin框架,必须首先创建一个ForkJoin任务。它提供在任务中执行fork()和join的操作机制,通常我们不直接继承ForkjoinTask类,只需要直接继承其子类。
1. RecursiveAction,用于没有返回结果的任务
2. RecursiveTask,用于有返回值的任务
task要通过ForkJoinPool来执行,分割的子任务也会添加到当前工作线程的双端队列中,进入队列的头部。当一个工作线程中没有任务时,会从其他工作线程的队列尾部获取一个任务。
ForkJoin框架使用了工作窃取的思想(work-stealing),算法从其他队列中窃取任务来执行。
ForkJoinPool:有三个方法来执行ForkJoinTask,分别为:execute(异步,不返回结果)/invoke(同步,返回结果)/submit(异步,返回结果)。

现在假如有一个List<Map<String,Object>> listMapDate的一个集合,我们需要将他写到一个txt文件里面,代码就可以这样编写:

public class ForkJoinWorkTask extends RecursiveTask<Integer> {
    /**
     * 阈值
     */
    private static final int THRESHOLD = 2;

    private List<Map<String, Object>> listMap;

    public ForkJoinWorkTask(List<Map<String, Object>> listMap) {
        this.listMap = listMap;
    }

    @Override
    protected Integer compute() {

        if (listMap.size() <= THRESHOLD) {
            return this.computeDirectly();
        } else {
            //得到listMap的数量,如果要处理的数据大于2,则进行分组插入
            int iListMapSize = listMap.size();
            //第一个分组
            ForkJoinWorkTask aForkJoinWorkTask = new ForkJoinWorkTask(listMap.subList(0, iListMapSize / 2));
            //第二个分组
            ForkJoinWorkTask bForkJoinWorkTask = new ForkJoinWorkTask(listMap.subList(iListMapSize / 2, iListMapSize));
            //两个任务并发执行起来
            invokeAll(aForkJoinWorkTask, bForkJoinWorkTask);

            //两个分组的插入的行数加起来
            return aForkJoinWorkTask.join() + bForkJoinWorkTask.join();
        }
    }

    /**
     * 业务处理逻辑
     */
    private int computeDirectly() {
//        try {
//            Thread.sleep((long) (records.size() * 1000));
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        System.out.println("处理了:" + Arrays.toString(listMap.toArray()));
        try {
            // 相对路径,如果没有则要建立一个新的文件
            File writeFile = new File("D:/pint_channel1.txt");
            if (!writeFile.exists()) {
                // 创建新文件,有同名的文件的话直接覆盖
                writeFile.createNewFile();
            }
            BufferedWriter out = new BufferedWriter(new OutputStreamWriter(
                    new FileOutputStream(writeFile, true)));
            try {
                for (int i = 0; i < listMap.size(); i++) {
                    Map<String,Object> map = listMap.get(i);
                    StringBuilder stringBuilder = new StringBuilder();
                        stringBuilder.append(map.get("name")).append(",");
                        stringBuilder.append(map.get("orderid"));

                    // \r\n即为换行
                    out.write(stringBuilder.toString() + "\r\n");
                }
                out.flush(); // 把缓存区内容压入文件
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                out.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return listMap.size();
    }
}

编写ForkJoinTask任务的关键在于,在执行任务的compute()方法内部,先判断任务是不是足够小,如果足够小,就直接就直接调用业务处理的方法computeDirectly(),否则,把自身任务一拆为二,分别计算两个子任务,再调用业务处理的方法。

  //第一个分组
  ForkJoinWorkTask aForkJoinWorkTask = new ForkJoinWorkTask(listMap.subList(0, iListMapSize / 2));
 //第二个分组
 ForkJoinWorkTask bForkJoinWorkTask = new ForkJoinWorkTask(listMap.subList(iListMapSize / 2, iListMapSize));
//两个任务并发执行起来
invokeAll(aForkJoinWorkTask, bForkJoinWorkTask);

这边需要注意分裂的处理,这边有一个fork()的方法和invokeAll()的方法,这两个方法是有区别的。
下面打个比方 :
ForkJoin的工作模式就像这样:首先,工人甲被分配了400个房间的任务,他一看任务太多了自己一个人不行,所以先把400个房间拆成两个200,然后叫来乙,把其中一个200分给乙。
紧接着,甲和乙再发现200也是个大任务,于是甲继续把200分成两个100,并把其中一个100分给丙,类似的,乙会把其中一个100分给丁,这样,最终4个人每人分到100个房间,并发执行正好是1天。
invokeAll的N个任务中,其中N-1个任务会使用fork()交给其它线程执行,但它还会留一个任务自己执行,这样,就充分利用了线程池,保证没有空闲的不干活的线程。

如果换成 :

aForkJoinWorkTask.fork();
bForkJoinWorkTask.fork();

甲把400分成两个200后,这种写法相当于甲把一个200分给乙,把另一个200分给丙,然后,甲成了监工,不干活,等乙和丙干完了他直接汇报工作。乙和丙在把200分拆成两个100的过程中,他俩又成了监工,这样,本来只需要4个工人的活,现在需要7个工人才能1天内完成,其中有3个是不干活的。

最后写一个main方法进行测试,这个mian方法用的submit(异步,返回结果)

 public static void main(String[] args) throws ExecutionException, InterruptedException {
        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
        try {
            for(int i = 0 ;i < 200000;i++) {
                Map<String, Object> map = new HashMap<>();
                map.put("name", "ForkJoinWorkTask : " + i);
                map.put("orderid", "ForkJoinWorkTask : id" + i);
                //获取其实内容
                list.add(map);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        ForkJoinPool forkJoinPool = new ForkJoinPool(8);
        ForkJoinWorkTask forkJoinWorkTask = new ForkJoinWorkTask(list);
        long t1 = System.currentTimeMillis();
        ForkJoinTask<Integer> reslut = forkJoinPool.submit(forkJoinWorkTask);
        System.out.println(reslut.get());
        long t2 = System.currentTimeMillis();
        System.out.println(t2 - t1);
    }

上面有些内容由别处参考而来,参考地址。
[1]: https://www.liaoxuefeng.com/article/1146802219354112

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值