首页 > 编程语言 >【Java并发编程线程池】 ForkJoinPool 线程池是什么 怎么工作的 和传统的ThreadPoolExecutor比较

【Java并发编程线程池】 ForkJoinPool 线程池是什么 怎么工作的 和传统的ThreadPoolExecutor比较

时间:2024-12-30 22:42:02浏览次数:1  
标签:10 Java 队列 ForkJoinPool 任务 线程 new

Java 中的 ForkJoinPool 线程池是什么 怎么工作的

Java 中的 ForkJoinPool 线程池是什么 怎么工作的

相比较于传统的线程池,ForkJoinPool 线程池更适合处理大量的计算密集型任务,它的核心思想是将一个大任务拆分成多个小任务,然后将这些小任务分配给多个线程去执行,最后将这些小任务的结果合并起来,得到最终的结果。

工作窃取

值得注意的,ForkJoinPool中的每个线程都有自己的任务队列,当线程任务队列空了或者当前线程空闲,则会去别的线程的任务队列获取待执行任务,这个过程是工作窃取。

怎么工作的

(一):传统线程池

Java中传统线程池(ThreadPoolExecutor)的工作流程的描述:

以 new ThreadPoolExecutor(2, 4, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10)) 为例,线程池的工作流程如下:

代码用例:

public static void main1(String[] args) {
    final var poolExecutor = new ThreadPoolExecutor(
            2, // 核心线程数
            4, // 最大线程数
            60,// 线程空闲时间
            TimeUnit.SECONDS, // 时间单位
            new ArrayBlockingQueue<>(10), // 任务队列
            new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
    );
    // 定义一个Runnable
    MyRunnable runnable = new MyRunnable();
    // 提交任务
    for (int i = 0; i < 10; i++) {
        poolExecutor.submit(runnable);
    }
}

static class MyRunnable implements Runnable {
    @SneakyThrows
    @Override
    public void run() {
        Thread.sleep(1000);
        System.out.println("Hello, world!");
    }
}
  1. 提交任务;
  2. 线程池中的线程数量小于 corePoolSize(核心线程数)时,不管有没有空闲的线程,都会创建一个新的线程来执行任务;
  3. 线程池中的线程数量等于 corePoolSize 时,若没有空闲线程,则任务会被放入任务队列中,等待线程池中的线程调度执行;
  4. 核心线程数已满 且 任务队列已满时,线程数量小于 maximumPoolSize(最大线程数)时,会创建新的线程来执行任务;
  5. 最后若是核心线程数、任务队列、最大线程数都满了,会根据拒绝策略来处理新任务。

图解:
传统ThreadPoolExecutor执行示意图

(二):ForkJoinPool 线程池

ForkJoinPool工作流程的描述:

以new ForkJoinPool(4) 和 普通的任务 为例,ForkJoinPool的工作流程如下:

代码用例:

public static void main(String[] args) {
    final var forkJoinPool = new ForkJoinPool(4);// 这个参数 “4” 的作用指定线程池的线程数量
    // 定义一个Runnable
    MyRunnable runnable = new MyRunnable();
    // 提交任务
    for (int i = 0; i < 10; i++) {
        forkJoinPool.submit(runnable);
    }
}
  1. 提交任务;(初次执行时,会做一些初始化工作,如创建任务队列的数组)
  2. 线程数量小于指定的线程数量时,会创建新的线程来执行任务,且每个线程都有自己的任务队列,区分于传统线程池只有一个任务队列,ForkJoinPool的线程池里的每个线程都有自己的任务队列;
  3. 每个线程从自己的任务队列中取出任务执行;
  4. 每个任务执行完毕后,且自己的任务队列为空时,会从其他线程的任务队列中偷取任务执行,这里被称作为“工作窃取”;

图解:
ForkJoinPool运行示意图

怎么体现将大任务拆分成小任务

在刚刚的代码用例中,我们提交了10个任务,但是我们的任务是一个普通的任务,没有体现将大任务拆分成小任务,下面我们换一个任务,体现将大任务拆分成小任务。

任务要求

要求计算 1 ~ 100w 的阶和,就是 "1+2+3+...+100w".

任务拆分方案:

我们可以将这个大任务拆分成多个小任务,将任务分配小到数的差值为10及以内时不再拆分,每个小任务计算区间的和,最后将这些小任务的结果合并起来,得到最终的结果。
如下

1:100w 的阶和 = 1w 的阶和 + 2w 的阶和 + 3w 的阶和 + ... + 10w 的阶和
2:10w 的阶和 = 1w 的阶和 + 2w 的阶和 + 3w 的阶和 + ... + 10w 的阶和
3:以此类推,直到区间的差值小于等于10时,不再拆分。
如100的阶和 = 1~10 的阶和 + 11~20 的阶和 + ... + 91~100 的阶和,其中 1~10的差值不大于10,不再拆分。

代码用例:

public static void main(String[] args) {
    // 任务要求:计算 1 ~ 100w 的阶和,就是 "1+2+3+...+100w".
    // 1. 创建一个 ForkJoinPool 对象
    ForkJoinPool forkJoinPool = new ForkJoinPool(4);
    // 2. 提交一个任务
    Integer result = forkJoinPool.invoke(new MyTask(1, 1000000));
    System.out.println("result = " + result);
}

private static class MyTask extends RecursiveTask<Integer> {
    private static final int THRESHOLD = 10;// 拆分最小任务的阈值
    private int begin;// 计算的区间的开始
    private int end;// 计算的区间的结束
    public MyTask(int begin, int end) {
        this.begin = begin;
        this.end = end;
    }

    @Override
    protected Integer compute() {
        if (end - begin <= THRESHOLD) {
            int sum = 0;
            for (int i = begin; i <= end; i++) {
                sum += i;
            }
            return sum;
        } else {
            int mid = (begin + end) / 2;
            MyTask left = new MyTask(begin, mid);
            MyTask right = new MyTask(mid + 1, end);
            left.fork();// 提交到任务队列
            right.fork();
            return left.join() + right.join();// join等待结果,线程会出现空闲,会去别的任务队列窃取任务执行
        }
    }
}

场景(常用于哪)

最常见、常用的场景中,其实就是在我们的Stream API中,我们使用了parallelStream()方法,底层就是使用了ForkJoinPool线程池。还有就是Java中新特性“虚拟线程”也是基于ForkJoinPool线程池实现的。

标签:10,Java,队列,ForkJoinPool,任务,线程,new
From: https://www.cnblogs.com/seazhan/p/18642634

相关文章

  • 线程的创建有哪些方式?各有什么特点?
    继承Thread类:简单易用,但灵活性差。实现Runnable接口:灵活性高,适合资源共享。使用Callable接口配合FutureTask:支持返回结果和异常处理,适合需要任务结果的场景。使用线程池:高效管理线程资源,适合处理大量短生命周期任务。使用CompletableFuture:底层依然依赖于线程池。支持......
  • Django Admin 中实现动态表单:无 JavaScript 解决方案
    引言在开发Web应用时,我们经常需要创建动态表单,即根据用户的输入动态更新其他字段的选项。通常,这种功能会使用JavaScript来实现。但是,在某些情况下,我们可能希望避免使用客户端脚本,而完全依赖服务器端逻辑。本文将介绍如何在DjangoAdmin中实现这样的动态表单,而无需使......
  • 《Java核心技术 I》容易忽视和重要的知识点汇总
    本文对《Java核心技术I》中开发者容易忽视和重要的知识点进行总结,不包含一般开发者都知道内容。大标题后括号的数字代表书中对应的章节。一、Java的基本程序设计结构(3)1.整数表示可以为数字字面量加上下划线,这些下划线只是为了让人更易读。Java编译器会去除这些下划线。intn......
  • Java Web学生自习管理系统
    一、项目背景与需求分析随着网络技术的不断发展和学校规模的扩大,学生自习管理系统的需求日益增加。传统的自习管理方式存在效率低下、资源浪费等问题,因此,开发一个智能化的学生自习管理系统显得尤为重要。该系统旨在提高自习室的利用率和管理效率,为学生提供方便快捷的自习预约服务......
  • Java 项目、模块、包
    1.IntelliJIDEA项目结构工程是顶级结构单元,在一个工程下可以创建多个模块,不同模块之间存在依赖关系,一个模块可以创建多个包,一个包可以创建多个类project(工程)-->module(模块)-->package(包)-->class(类)1.1创建项目File-->New-->Project,创建一个名为Java_Proj......
  • Java难绷知识04——异常处理中的finally块
    Java难绷知识04——异常处理中的finally块前情提要:该文章是个人花的时间最长,查询资料最多,可能是有关finally块的最长文章,希望大家能看下去一些前言在Java中,异常处理机制是程序设计中至关重要的一部分。它允许程序员在程序运行时捕获并处理错误,防止程序因为异常情况而突然崩溃。......
  • 基于Java的网络音乐系统的设计与实现
             摘 要互联网发展至今,无论是其理论还是技术都已经成熟,而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播,搭配信息管理工具可以很好地为人们提供服务。针对音乐信息管理混乱,出错率高,信息安全性差,劳动强度大,费时费力等问题,采用网络音乐系......
  • 【Java编程】JDBC 底层原理
    概述JDBC(JavaDataBaseConnectivity)是Java和数据库之间的一个桥梁,是一个规范而不是一个实现,能够执行SQL语句。JDBC由一组用Java语言编写的类和接口组成。各种不同类型的数据库都有相应的实现,注意:本文中的代码都是针对MySQL数据库实现的。先看一个案例:publicclassJdbcDemo{......
  • 解锁 Java 解释器模式:赋予程序理解 “新语言” 的魔力
    解锁Java解释器模式:赋予程序理解“新语言”的魔力在Java编程的广袤天地中,我们时常面临需要处理自定义规则、语法或逻辑表达式的场景。此时,解释器模式(InterpreterPattern)宛如一位神奇的翻译官,能够将这些看似晦涩难懂的“新语言”,转化为计算机能够理解并执行的指令,为......
  • 揭秘 Java 中介者模式:解耦复杂交互的神奇钥匙
    揭秘Java中介者模式:解耦复杂交互的神奇钥匙在Java开发的浩瀚天地里,随着系统复杂度的与日俱增,对象之间的交互常常变得错综复杂,宛如一团乱麻。此时,中介者模式(MediatorPattern)宛如一位智慧的协调大师,挺身而出,为我们理清理顺这些复杂的关系,打造更为优雅、易于维护的代码架......