深入理解并发编程之线程池Fork Join

深入理解并发编程之线程池Fork Join


一、什么是Fork Join

或许初级程序员不知道什么是Fork Join,我们先简单的举个例子来说明下:

假设我们需要群发10W封邮件,我们可能采用哪种方法呢?有人可能说使用线程或者线程池,每个线程发送1W封邮件,这样做肯定是没问题的,假设我们不是发邮件呢,可能是操作10W条复杂数据处理,而且处理过程根据数据情况处理的复杂度不同,这样就是10W条执行时间不同的数据,我们使用了10个线程,这就出现了个小问题,如果前面几万的数据比较简单处理的快,后面的几万条数据复杂处理时间是前面几万数据的3倍时间吧,那么我们前面几个线程就会先执行完毕,最终一直在等待后面的线程执行结束,有没有更好的办法让他们尽可能去平均分配时间执行呢?这就需要用到了Fork Join。

Fork Join是Java7提供的并行执行任务的框架,是一个把大任务分割成若干小任务,小任务可以继续不断拆分n多个小任务,最终汇总小任务的结果得到大任务结果的框架。

二、举例说明下Fork Join的用法

我们用3个线程的Fork Join线程池求1到10W的和来简单的看一下:

public class ForkJoinTest extends RecursiveTask<Long> {
    private Long start;		//计算的开始值
    private Long end;		//计算的最终值
    private Long max = 100L; //计算区间的最大差值
    private static AtomicInteger worker1 = new AtomicInteger(0); //分别记录每个线程执行次数
    private static AtomicInteger worker2 = new AtomicInteger(0);
    private static AtomicInteger worker3 = new AtomicInteger(0);


    public ForkJoinTest(Long start, Long end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
        Long sum = 0L;
        //如果计算的区间小于100直接计算
        if (end - start < max) {
            if ("ForkJoinPool-1-worker-1".equals(Thread.currentThread().getName())) {
                worker1.incrementAndGet(); //如果线程1执行就++
                System.out.println("worker1:" + worker1.get());
            }
            if ("ForkJoinPool-1-worker-2".equals(Thread.currentThread().getName())) {
                worker2.incrementAndGet(); //如果线程2执行就++
                System.out.println("worker2:" + worker2.get());
            }
            if ("ForkJoinPool-1-worker-3".equals(Thread.currentThread().getName())) {
                worker3.incrementAndGet(); //如果线程3执行就++
                System.out.println("worker3:" + worker3.get());
            }
            for (Long i = start; i <= end; i++) {
                sum += i;
            }
        } else {
            // 如果计算的区间>=100,分两半继续分直到分的区间小于100为止
            Long l = (start + end) / 2; 
            ForkJoinTest left = new ForkJoinTest(start, l);  //执行前面的一半
            //Fork()方法 Fork()方法类似于Thread.start(),但是它并不立即执行任务,而是将任务放入工作队列中,拆分子任务。
            left.fork();
            ForkJoinTest rigt = new ForkJoinTest(l + 1, end); //执行后面的一半
            rigt.fork();
            try {
                sum = left.get() + rigt.get(); //每一半的最终结果加起来
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
        return sum;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ForkJoinTest forkJoinTest = new ForkJoinTest(1L, 10000L);
        ForkJoinPool forkJoinPool = new ForkJoinPool(3); //线程池有3个线程
        ForkJoinTask<Long> submit = forkJoinPool.submit(forkJoinTest);
        System.out.println("执行结果为:" + submit.get());
    }
}

可以看一下计算结果:
在这里插入图片描述
不考虑线程的话我们仔细看一下上面代码的逻辑,其实就是一个递归操作,我们一直往下递归把10000拆分成每部分都区间小于100的多个部分进行计算,然后把最后的和汇总起来。其实Fork Join就是使用多线程的递归,首先设置个预期值,如果在预期值内直接执行,如果不在预期值内则继续递归,往下继续递归任务。
在这里插入图片描述
Fork Join继承的有两个类,RecursiveTask是有返回值的,RecursiveAction是没有返回值的。
Fork Join有两个核心方法:

  • fork(): 用于将新创建的子任务放入当前线程的work queue队列中,这个概念类似于线程池,fork()方法会把任务放入工作队列中,子线程中使用invokeAll()比fork()效率高。
  • join(): 用于让当前线程阻塞,直到对应的子任务完成运行并返回执行结果。

Fork Join有三种提交任务的方式:

  • 通过invoke方法提交的任务,调用线程直到任务执行完成才会返回,也就是说这是一个同步方法,且有返回结果
  • 通过execute方法提交的任务,调用线程会立即返回,也就是说这是一个异步方法,且没有返回结果
  • 通过submit方法提交的任务,调用线程会立即返回,也就是说这是一个异步方法,且有返回结果(返回Future实现类,可以通过get获取结果。

内容来源:蚂蚁课堂

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
学习Java并发编程是一项需要投入大量时间和精力的任务,但它在现代软件开发中是不可或缺的。通过深入学习线程、同步和异步、互斥和死锁、线程安全性、并发编程模型以及Java并发包等内容,您可以成为一名更加优秀的编程专家。 对于初学者来说,Java并发编程可能会显得晦涩难懂,并且涉及了许多新技术。在学习这一领域之前,建议掌握好相关的理论知识,并打好基础。只有在掌握了基础知识后,才能更好地理解和应用更高层次的概念。 在进行Java并发编程的训练时,以下是一些建议: 1. 了解并掌握Java中的线程机制和线程生命周期。 2. 学习如何使用同步和异步机制来处理并发编程中的数据共享和通信问题。 3. 理解并学习如何使用锁、互斥和死锁的概念以及如何避免它们。 4. 熟悉线程安全性的概念和相关的技术,如volatile关键字和Atomic类。 5. 学习并理解Java提供的并发编程模型,如线程池Fork/Join框架。 6. 了解并掌握Java并发包中提供的各种类和工具,如Semaphore、CountDownLatch和CyclicBarrier等。 通过不断学习和练习,并发编程的技能会得到提升。建议您多实践编写并发程序,通过解决实际问题来加深对并发编程的理解。祝您在学习Java并发编程的过程中取得成功!<span class="em">1</span><span class="em">2</span> #### 引用[.reference_title] - *1* *2* [如何学习Java并发编程](https://blog.csdn.net/weixin_42080277/article/details/129785094)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值