JAVA多线程进阶篇 12、JUC线程池之ForkJoinPool

1. 为什么要使用线程池?

如果放任线程无限制地创建,会耗尽CPU资源,并且降低系统的响应速度。
为了更好的对线程进行管理,实现线程的统一分配,调优和监控,降低资源消耗。
JUC设计了线程池。线程池从机制上分为两种.,一是ThreadPoolExecutor,另一类是ForkJoinPool。

  • ThreadPoolExecutor
    使用多个线程和阻塞队列实现对线程和任务进行管理的工具。
  • ForkJoinPool
    将一个大任务拆分为很多小任务来异步执行的管理的工具。

1.1 Executor 和 ExecutorService

ThreadPoolExecutor 和 ForkJoinPool 都实现了Executor 和 ExecutorService接口。
Executor 和 ExecutorService接口规定了对线程池使用的提交任务和关闭任务的方法。

1.2 线程池提交任务

  • execute()方法是Executor接口规定的。只能接受Runnable类型的任务。
    void execute(Runnable command);
  • submit()方法是ExecutorService接口规定的。有返回值,且可以通过Future.get()抛出Exception
    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
    Future<?> submit(Runnable task);

1.3 线程池关闭

  • shutdown()方法,shutdown只是将线程池的状态设置为SHUTWDOWN状态,正在执行的任务会继续执行下去,没有被执行的则中断。

  • shutdownNow(),而shutdownNow则是将线程池的状态设置为STOP,正在执行的任务则被停止,没被执行任务的则返回。

    shutdownNow会停止正在运行的线程。

2. ForkJoinPool

2.1 ForkJoinPool原理

ForkJoinPool使用分治算法(Divide-and-Conquer)把任务递归的拆分为各个子任务,,尽可能的使用所有可用的计算能力来提升应用性能。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pIbUykHP-1647316188651)(https://s2.loli.net/2022/03/15/YMZaRk17vzgGxVJ.png)]

2.2 ForkJoinTask

ForkJoinPool 只接收 ForkJoinTask 任务。我们一般都会继承 RecursiveTaskRecursiveActionCountedCompleter 来实现我们的业务需求,而不会直接继承 ForkJoinTask 类。

  • RecursiveTask 是 ForkJoinTask 的子类,是一个可以递归执行的 ForkJoinTask。
  • RecursiveAction 是一个无返回值的 RecursiveTask。
  • CountedCompleter 在任务完成执行后会触发执行一个自定义的钩子函数。

2.3 继承实现一个RecursiveTask

继承RecursiveTask 需要 重写其compute()方法,该方法的返回值是泛型。

当任务较大的时候 就将任务切分,当任务规模能够直接处理就在本地处理,并返回结果。

RecursiveTask2类,是自定义的实现简单的数字累加。当计算规模在100个数以内,就直接使用循环计算。如果大于100个数,将划分为两个部分,并创建两个RecursiveTask2对象fork()后再join()。

public class RecursiveTask2 extends RecursiveTask<Integer> {

    int start,end;
    static final int MAXNUM=100;

    public RecursiveTask2(int start, int end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Integer compute() {
        if(end-start>MAXNUM) {
            int mid = start+(end-start)/2;
            RecursiveTask2 task1 = new RecursiveTask2(start,mid);
            RecursiveTask2 task2 = new RecursiveTask2(mid,end);
            task1.fork();
            task2.fork();
            return task1.join()+task2.join();
        }
        else {
            int sum = 0;
            while (start<end) {
                start++;
                sum+=start;
            }
            return sum;
        }
    }
}

2.4 使用ForkJoinPool计算RecursiveTask任务

将自定义的任务提交到ForkJoinPool。ForkJoinPool将不断创建子线程任务,直到每个线程能计算的任务规模在预定的规模下,然后再汇总计算结果。

public class ForkJoinPoolTest2 {
    public static void main(String[] args) {
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        RecursiveTask2 recursiveTask2 = new RecursiveTask2(0, 1000);
        forkJoinPool.execute(recursiveTask2);
        System.out.println(recursiveTask2.join());
        try {
            System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

500500

2.5 WorkStealingPool

Executors工具类也提供了创建ForkJoinPool的方法。源码如下:

 public static ExecutorService newWorkStealingPool() {
        return new ForkJoinPool
            (Runtime.getRuntime().availableProcessors(),
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }

WorkStealingPool是一类比较特殊的ForkJoinPool。每个线程分到自己的任务后,如果发现自己的任务已完成那么它可以“窃取”其他线程未完成的任务。进一步提升了效率和使用率。其原理如下

  • 每个线程都有自己的一个WorkQueue,该工作队列是一个双端队列。划分的子任务调用fork时,都会被push到自己的队列中。

  • 所有者线程调用pop方法,从WorkQueue提取任务。

  • 当线程完成了任务,自己所属的WorkQueue为空。则随机从另一个线程的队列末尾调用poll方法窃取任务。

public class ForkJoinPoolTest3 {
    public static void main(String[] args) {
        ExecutorService forkJoinPool = Executors.newWorkStealingPool(20);
        for(int i=0;i<100;i++) {
            forkJoinPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        forkJoinPool.shutdown();;
    }
}

总结

ForkJoinPool使用分治算法(Divide-and-Conquer)把任务递归的拆分为各个子任务,,尽可能的使用所有可用的计算能力来提升应用性能。

多线程系列在github上有一个开源项目,主要是本系列博客的实验代码。

https://github.com/forestnlp/concurrentlab

如果您对软件开发、机器学习、深度学习有兴趣请关注本博客,将持续推出Java、软件架构、深度学习相关专栏。

您的支持是对我最大的鼓励。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

悟空学编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值