Java基础之Executor框架&线程池介绍

目录

一 Executor 介绍

1. Executor 框架的作用

2. Java 线程池的功能

3. Executor 框架与线程池的关系

4. Executor 框架的优势

5. 适用场景

二 Executor架构

1. Executor框架概述

2. Executor框架架构

(1)顶层接口

(2)抽象实现

(3)具体实现

三 ThreadPoolExecutor 详解

1. 线程池的使用方法

(1)普通使用示例

(2)设置线程池的各个参数示例

(3)线程池关闭

2. ThreadPoolExecutor 的实现原理

(1)线程池的任务执行流程

(2)核心参数的动态调整

(3)线程池的关闭与回收

四 ScheduledThreadPoolExecutor详解

1 使用示例

2 底层原理

(1) ScheduledThreadPoolExecutor 继承关系

(2) 核心组件

3 scheduleAtFixedRate vs scheduleWithFixedDelay

4 异常处理

五 Fork/Join 框架

1 Fork/Join 框架的作用

(1) 适用场景

(2) 核心组件

2 Fork/Join 的基本使用模式

(1) Fork/Join 的工作流程

(2) Fork/Join 代码示例

3 Fork/Join 框架的底层实现原理

(1) 工作窃取(Work Stealing)算法

(2) Fork/Join 任务调度机制

(3) ForkJoinPool 线程池结构

4 Fork/Join 和 线程池对比

(1) Fork/Join 与 ThreadPoolExecutor 区别

(2) 适用场景对比


一 Executor 介绍

        在现代计算机系统中,程序的执行通常需要多个任务同时进行,尤其是在多核处理器的环境下,高效地利用计算资源变得尤为重要。Java 提供了丰富的并发工具,而 Executor 框架正是其中专门用于管理和优化线程执行的核心组件。它不仅简化了线程的创建和管理,还提供了一种更为灵活和高效的方式来处理并发任务。

1. Executor 框架的作用

        Executor 框架的主要作用是解耦任务提交与任务执行的过程。传统的线程管理方式通常依赖于手动创建线程并调用 start() 方法启动任务,这种方式不仅增加了代码的复杂性,还可能导致资源管理问题,如线程的频繁创建和销毁带来的性能开销,以及难以控制的并发执行数量。而 Executor 框架提供了一种更为系统化的方式,通过线程池的概念,控制任务的调度、执行和管理,使得开发人员可以专注于任务逻辑,而无需关心底层线程的管理细节。

        在 Executor 框架的作用下,任务的提交与执行被分离,开发者可以使用统一的接口提交任务,而框架本身决定如何执行这些任务。这种机制带来了更高的灵活性,使得线程管理更加智能化,同时也提供了更好的可扩展性,适用于不同规模的并发场景。

2. Java 线程池的功能

        线程池是 Executor 框架的重要组成部分,其核心功能在于维护一组可复用的线程,并提供任务的调度和管理。相比于传统的手动线程管理方式,线程池能够有效地控制线程的数量,避免资源浪费,并减少频繁创建和销毁线程所带来的开销。 

  • 线程池的主要功能

    • 线程复用:线程池会维护一定数量的线程,当有新的任务提交时,会尝试使用已有的空闲线程执行任务,而不是每次都创建新线程,从而减少线程创建和销毁的开销。

    • 任务队列管理:线程池内部通常会维护一个任务队列,在所有工作线程忙碌时,新提交的任务会暂存在队列中,等待线程可用时再执行。

    • 线程生命周期管理:线程池会自动管理线程的生命周期,包括线程的创建、执行、空闲回收等,确保系统资源的高效利用。

    • 并发控制:线程池提供了多种策略来控制任务的执行方式,如最大线程数、任务队列长度、拒绝策略等,确保系统在高负载情况下仍然能够稳定运行。

    • 调度执行:除了普通的任务执行外,某些线程池还支持任务的定时执行和周期性调度,使得并发任务的调度更加灵活。

  • 线程池的核心原理

    1. 线程池组成

      • 核心线程数:始终存活的线程数量。
      • 最大线程数:线程池可创建的最大线程数。
      • 任务队列:存储待执行任务,减少线程创建。
      • 线程工厂:自定义线程创建。
      • 拒绝策略:超出容量时的任务处理方式。
    2. 任务执行流程

      1. 任务提交,优先使用空闲核心线程。
      2. 核心线程满时,任务入队列等待。
      3. 队列满时,创建新线程(不超过最大线程数)。
      4. 超限时,触发拒绝策略(丢弃、异常或由主线程执行)。
    3. 线程管理

      • 空闲线程超过存活时间自动回收(核心线程可选回收)。
      • 任务调度采用 FIFO/LIFO 队列,保证并发安全。
    4. 性能优化

      • 合理配置线程数:避免资源浪费或 CPU 过载。
      • 选择合适队列:有界队列防 OOM,无界队列适合短任务。
      • 优化拒绝策略:防止任务堆积影响系统稳定性。
  • 线程池的使用步骤

    1. 选择线程池类型:根据需求选择合适的线程池,如固定大小 (FixedThreadPool)、缓存 (CachedThreadPool)、单线程 (SingleThreadExecutor)、定时任务 (ScheduledThreadPool) 等。

    2. 创建线程池:使用 ExecutorsThreadPoolExecutor 创建线程池实例。

    3. 提交任务:通过 execute() 提交 Runnable 任务,或使用 submit() 提交 Callable 任务以获取返回值。

    4. 监控线程池:获取活跃线程数、任务队列大小等信息,优化线程池配置。

    5. 关闭线程池:调用 shutdown() 等待任务完成后关闭,或 shutdownNow() 立即终止任务。

    6. 处理任务结果(可选):对于 Callable 任务,可使用 Future.get() 获取执行结果。

3. Executor 框架与线程池的关系

        在 Java 并发体系中,Executor 框架与线程池密切相关,可以说线程池是 Executor 框架最核心的实现方式之一。Executor 作为一个高级抽象,定义了任务执行的标准接口,而线程池则是具体的实现,它通过线程复用、任务队列管理和线程生命周期控制等机制,提供了一种高效的并发执行方式。

        从架构上来看,Executor 框架提供了一系列接口和实现类,其中最核心的组件包括:

  • Executor 接口:定义了任务执行的基本方法,所有的任务执行器都需要实现该接口。
  • ExecutorService 接口:在 Executor 的基础上增加了更丰富的管理功能,如提交任务、获取执行结果、关闭线程池等。
  • ThreadPoolExecutor 类ExecutorService 的主要实现类,它提供了线程池的核心功能,如线程管理、任务调度、拒绝策略等。
  • ScheduledThreadPoolExecutor 类:提供了定时任务执行和周期任务调度的能力,是 ExecutorService 的扩展实现。

        可以看出,Executor 作为一个框架,为不同类型的任务执行需求提供了统一的接口,而线程池作为核心实现,承担了实际的任务调度和线程管理工作。这种设计不仅提高了系统的灵活性,还让开发者能够根据具体需求选择合适的线程池类型,从而实现更高效的并发处理。

4. Executor 框架的优势

        Executor 框架的出现,为 Java 并发编程带来了诸多优势,主要体现在以下几个方面:

  • 降低开发复杂度:开发者不需要手动管理线程的创建和销毁,而是通过提供的接口提交任务,由框架负责调度和管理,大大简化了并发程序的编写。
  • 提高资源利用率:通过线程池机制,避免了线程的频繁创建和销毁,降低了 CPU 和内存的消耗,提高了系统资源的利用率。
  • 提升系统稳定性Executor 框架内置了任务队列和线程管理策略,能够有效控制并发任务的执行,防止系统因线程过多而崩溃。
  • 提供灵活的扩展性:开发者可以根据具体需求,自定义线程池的参数,如核心线程数、最大线程数、任务队列类型等,使得线程管理更加可控。
  • 支持任务调度:除了普通的线程池,Executor 还提供了 ScheduledThreadPoolExecutor,支持任务的定时执行和周期性调度,适用于定时任务管理。
5. 适用场景

        Executor 框架的应用场景非常广泛,几乎所有涉及多线程并发处理的系统都可以使用它来优化性能。以下是几个典型的应用场景:

  • Web 服务器和应用服务器:在 Web 应用中,服务器需要处理大量并发请求,如果每个请求都创建一个新线程,系统很快会因为线程数过多而崩溃。使用线程池可以有效控制并发数量,提高系统的吞吐量和稳定性。
  • 批量任务处理:在数据处理、日志分析、文件处理等场景中,通常需要对大量任务进行并发执行,Executor 框架能够高效管理这些任务,提高执行效率。
  • 异步任务执行:在某些场景下,程序需要异步执行一些耗时任务,如网络请求、数据库查询等,使用 Executor 框架可以避免阻塞主线程,提高应用的响应速度。
  • 定时任务调度:在定时任务管理场景,如定期清理缓存、定时数据同步等,可以使用 ScheduledThreadPoolExecutor 进行周期性调度。
  • 计算密集型任务:在科学计算、人工智能等高计算需求的场景下,可以使用 Fork/Join 框架进行任务拆分,提高计算效率。

二 Executor架构

1. Executor框架概述

        Executor 是 Java 并发包(java.util.concurrent)提供的任务执行框架,旨在 解耦任务提交与执行,提高线程管理的灵活性,避免手动创建和销毁线程的高昂开销。它提供了一套 标准化的线程池实现,用于 任务调度、线程复用和负载管理,适用于高并发环境。

        Executor 框架的核心组件主要围绕 接口层、抽象层和具体实现层 进行设计,形成一套 层次化的线程管理体系

2. Executor框架架构

        Executor 框架的架构从上到下可分为 顶层接口、抽象实现和具体实现,主要包括以下核心部分:

(1)顶层接口
  1. Executor(执行器)

    • 核心接口,定义任务执行的基本方法 execute(Runnable command)
    • 代表任务执行的抽象概念,屏蔽底层线程管理细节。
  2. ExecutorService(扩展执行器)

    • 继承 Executor,提供更丰富的线程管理功能,如 任务提交 (submit())、任务取消 (shutdown() / shutdownNow())、批量任务执行 (invokeAll() / invokeAny()) 等。
  3. ScheduledExecutorService(定时任务执行器)

    • 继承 ExecutorService,支持 定时执行周期性执行任务schedule()scheduleAtFixedRate()scheduleWithFixedDelay())。
(2)抽象实现
  1. AbstractExecutorService(抽象执行器服务)

    • 提供 ExecutorService 的部分默认实现,简化线程池扩展。
    • 主要封装 submit() 方法,使 Runnable 任务适配 Callable
(3)具体实现
  1. ThreadPoolExecutor(核心线程池实现)

    • 最核心的线程池实现,支持灵活的线程池参数配置(线程数、队列、拒绝策略等)。

    • 采用 任务队列 + 线程管理 结合的方式,提高任务吞吐量和资源利用率。

  2. ScheduledThreadPoolExecutor(定时任务线程池)

    • 继承 ThreadPoolExecutor,扩展 定时任务和周期任务调度能力,用于实现 定时任务、延迟任务、周期任务 等功能。
  3. ForkJoinPool(Fork/Join 框架)

    • 适用于 大规模并行计算,基于 工作窃取(Work Stealing) 算法优化任务拆分与合并。

    • 继承 AbstractExecutorService,可用于递归拆分任务(RecursiveTaskRecursiveAction)。

  4. Executors(工具类)

    提供静态方法创建各种 常用线程池,包括:

    • newFixedThreadPool(n):固定大小线程池,适用于任务量稳定的场景。

    • newCachedThreadPool():缓存线程池,适用于短期大量任务的场景。

    • newSingleThreadExecutor():单线程池,确保任务按顺序执行。

    • newScheduledThreadPool(n):定时任务线程池,支持周期性任务执行。

        Executor 框架通过分层架构 (顶层接口 → 抽象实现 → 具体实现) 提供强大的线程管理能力,使 Java 并发编程更加灵活高效。在 java.util.concurrent 包下,它涵盖 线程池管理、任务调度、并行计算 等多个方面,极大简化了线程管理的复杂性。

三 ThreadPoolExecutor 详解

1. 线程池的使用方法

        ThreadPoolExecutor 是 Java 中常用的线程池实现,用于管理和调度线程执行任务。下面通过代码示例来介绍如何使用 ThreadPoolExecutor

(1)普通使用示例
import java.util.concurrent.*;

public class ThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个固定大小的线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
            5,   // 核心线程数
            10,  // 最大线程数
            60L, // 空闲线程存活时间
            TimeUnit.SECONDS, // 空闲时间单位
            new LinkedBlockingQueue<>(), // 任务队列
            new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
        );

        // 提交任务
        for (int i = 0; i < 20; i++) {
            executor.submit(new Task(i));
        }

        // 关闭线程池
        executor.shutdown();
    }

    // 定义任务
    static class Task implements Runnable {
        private final int id;

        public Task(int id) {
            this.id = id;
        }

        @Override
        public void run() {
            System.out.println("任务 " + id + " 被执行, 线程名: " + Thread.currentThread().getName());
        }
    }
}
  • 这个示例中,我们创建了一个具有 核心线程数 5最大线程数 10 的线程池,使用 LinkedBlockingQueue 作为任务队列,采用 CallerRunsPolicy 作为拒绝策略。
  • 通过 submit() 方法提交任务。线程池会在有空闲线程时执行任务,若线程池中没有空闲线程且队列已满,将根据拒绝策略处理新任务。
(2)设置线程池的各个参数示例
import java.util.concurrent.*;

public class ThreadPoolConfigExample {
    public static void main(String[] args) {
        // 设置线程池参数
        int corePoolSize = 4;      // 核心线程数
        int maximumPoolSize = 8;   // 最大线程数
        long keepAliveTime = 60L;  // 空闲线程存活时间
        TimeUnit timeUnit = TimeUnit.SECONDS; // 时间单位

        // 使用有界队列(ArrayBlockingQueue)限制队列大小
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(10);

        // 设置线程池工厂,可以自定义线程创建方式
        ThreadFactory threadFactory = Executors.defaultThreadFactory();

        // 设置拒绝策略(当线程池和队列都满时的处理方式)
        RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();

        // 创建线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
            corePoolSize, 
            maximumPoolSize, 
            keepAliveTime, 
            timeUnit, 
            workQueue, 
            threadFactory, 
            handler
        );

        // 提交任务
        for (int i = 0; i < 20; i++) {
            executor.submit(new Task(i));
        }

        // 关闭线程池
        executor.shutdown();
    }

    // 定义任务
    static class Task implements Runnable {
        private final int id;

        public Task(int id) {
            this.id = id;
        }

        @Override
        public void run() {
            System.out.println("任务 " + id + " 被执行, 线程名: " + Thread.currentThread().getName());
        }
    }
}

        这个示例演示了如何通过 ThreadPoolExecutor 提供的构造函数自定义线程池的各个参数,包括 核心线程数、最大线程数、空闲线程存活时间、任务队列、线程工厂拒绝策略

(3)线程池关闭
executor.shutdown();  // 优雅关闭,线程池会等待任务执行完再关闭
// 或者立即停止所有线程池中的任务
executor.shutdownNow();  // 立即停止所有任务,返回待执行的任务列表
2. ThreadPoolExecutor 的实现原理

        ThreadPoolExecutor 的设计思想是任务提交与线程管理的分离,通过线程池的合理配置和线程复用,减少了频繁创建和销毁线程的开销,提高了并发性能。下面是 ThreadPoolExecutor 的主要实现原理。

(1)线程池的任务执行流程
  1. 任务提交

    • 任务通过 submit()execute() 提交给线程池。
    • ThreadPoolExecutor 首先判断当前线程池中是否有足够的空闲线程来执行任务。
  2. 线程创建与执行

    • 如果当前的线程数小于 corePoolSize,则直接创建新线程执行任务。
    • 如果线程池中的线程数已经达到 corePoolSize,任务将会被 放入队列(workQueue) 等待执行。
    • 如果队列已满并且线程数未达到 maximumPoolSize,会继续创建新线程来处理任务。
    • 如果线程数已达到 maximumPoolSize,且队列已满,线程池会根据设置的 拒绝策略(RejectedExecutionHandler) 处理任务。
  3. 线程回收

    • 如果线程池中的线程数超过 corePoolSize,而且线程空闲时间超过 keepAliveTime,这些线程会被销毁。
    • 如果设置了 allowCoreThreadTimeOut(true),即使是核心线程也可以被回收。
  4. 任务队列的管理

    • BlockingQueue 存储待执行的任务,常用的队列类型有:
      • LinkedBlockingQueue:无界队列,适用于任务数目较大但执行时间较长的场景。
      • ArrayBlockingQueue:有界队列,适用于任务数目有限但执行时间短的场景。
      • SynchronousQueue:每次提交任务都需要立刻有线程来执行,适用于每个任务都必须由一个新的线程来处理的场景。
  5. 拒绝策略

    • 如果线程池无法处理新任务,会通过拒绝策略来处理。常见的拒绝策略有:
      • AbortPolicy:抛出 RejectedExecutionException 异常。
      • CallerRunsPolicy:调用者线程执行任务。
      • DiscardPolicy:丢弃任务。
      • DiscardOldestPolicy:丢弃队列中最旧的任务,尝试提交新的任务。
(2)核心参数的动态调整
  • 线程池的核心线程数 (corePoolSize) 和最大线程数 (maximumPoolSize) 是可以动态调整的。通过 setCorePoolSize(int)setMaximumPoolSize(int) 方法,可以在运行时调整线程池的规模。

  • 线程池的最大线程数的作用是限制线程池中线程的总数,防止线程数过多占用系统资源。

(3)线程池的关闭与回收
  • 线程池的关闭有两种方式:

    • shutdown():不再接受新任务,等待已提交的任务完成后关闭线程池。
    • shutdownNow():立即停止所有任务,并返回未执行的任务列表。
  • 线程池的回收通过定时检查空闲线程来进行,如果线程池中的线程数超过 corePoolSize 且空闲时间超过 keepAliveTime,就会销毁多余的线程。

        ThreadPoolExecutor 是 Java 并发编程中非常重要的组件,它有效地管理线程的创建、执行与回收,保证了线程池的高效运行。通过合理配置线程池的参数,能够适应不同类型的任务需求,提高程序的并发能力。

四 ScheduledThreadPoolExecutor详解

        ScheduledThreadPoolExecutor 是 Java 并发包(java.util.concurrent)中的一个线程池实现,它主要用于定时任务调度,支持 延迟执行周期性执行 任务。它比 Timer 更加强大,支持更灵活的调度策略,并且能更好地处理异常情况。

1 使用示例
import java.util.concurrent.*;

public class ScheduledThreadPoolExecutorDemo {
    public static void main(String[] args) {
        // 创建一个包含 3 个线程的定时任务线程池
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(3);

        // 提交一个任务,延迟 2 秒后执行
        scheduler.schedule(() -> {
            System.out.println("执行了一次任务: " + System.currentTimeMillis());
        }, 2, TimeUnit.SECONDS);

        // 提交一个任务,延迟 1 秒后开始,每 3 秒执行一次
        scheduler.scheduleAtFixedRate(() -> {
            System.out.println("固定速率执行任务: " + System.currentTimeMillis());
        }, 1, 3, TimeUnit.SECONDS);

        // 提交一个任务,延迟 1 秒后开始,每次任务执行完后等待 3 秒再执行下一次
        scheduler.scheduleWithFixedDelay(() -> {
            System.out.println("固定延迟执行任务: " + System.currentTimeMillis());
        }, 1, 3, TimeUnit.SECONDS);

        // 让主线程休眠 10 秒,观察任务执行情况
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 关闭线程池
        scheduler.shutdown();
    }
}
2 底层原理
(1) ScheduledThreadPoolExecutor 继承关系

        它是 ThreadPoolExecutor 的子类,内部通过 任务队列 进行任务调度:

java.lang.Object
  ├── java.util.concurrent.AbstractExecutorService
      ├── java.util.concurrent.ThreadPoolExecutor
          ├── java.util.concurrent.ScheduledThreadPoolExecutor
(2) 核心组件
  • DelayedWorkQueue(任务队列)

    ScheduledThreadPoolExecutor 采用 DelayedWorkQueue(基于最小堆的优先级队列) 存储任务。
    任务按 执行时间(deadline)排序,最近要执行的任务排在队列头部。

  • ScheduledFutureTask(定时任务封装)

    所有提交的任务都会被封装成 ScheduledFutureTask,它实现了

    • RunnableScheduledFuture<V>

    • RunnableFuture<V>

    • Delayed

    • Comparable<ScheduledFutureTask<?>>

  • 任务调度

    • 调用 schedule() 提交任务

      任务被封装成 ScheduledFutureTask,并存入 DelayedWorkQueue

    • 线程池从队列取出最近需要执行的任务
      如果任务的执行时间没到,就阻塞等待

    • 任务到期后线程池执行
      如果是周期性任务(scheduleAtFixedRate / scheduleWithFixedDelay),会重新计算下次执行时间并重新加入队列

3 scheduleAtFixedRate vs scheduleWithFixedDelay
方法任务调度策略适用场景
scheduleAtFixedRate任务 固定间隔执行(任务执行时间不影响间隔)适用于定时任务,确保固定速率执行,如 心跳检测
scheduleWithFixedDelay任务 执行完毕后等待固定时间再执行适用于依赖前一次执行结果的任务,如 日志归档

对比

scheduler.scheduleAtFixedRate(() -> {
    System.out.println("scheduleAtFixedRate 执行: " + System.currentTimeMillis());
}, 1, 3, TimeUnit.SECONDS);

scheduler.scheduleWithFixedDelay(() -> {
    System.out.println("scheduleWithFixedDelay 执行: " + System.currentTimeMillis());
}, 1, 3, TimeUnit.SECONDS);
  • scheduleAtFixedRate 任务即使超时,也不会影响间隔
  • scheduleWithFixedDelay 任务如果超时,会影响下次执行时间
4 异常处理

        如果 scheduleAtFixedRate 任务抛出异常,线程池不会捕获,后续任务也不会执行:

scheduler.scheduleAtFixedRate(() -> {
    System.out.println("执行任务...");
    throw new RuntimeException("任务异常!");
}, 1, 3, TimeUnit.SECONDS);

解决方案

使用 try-catch 捕获异常:

scheduler.scheduleAtFixedRate(() -> {
    try {
        System.out.println("执行任务...");
        throw new RuntimeException("任务异常!");
    } catch (Exception e) {
        System.err.println("任务执行异常: " + e.getMessage());
    }
}, 1, 3, TimeUnit.SECONDS);

五 Fork/Join 框架

1 Fork/Join 框架的作用
(1) 适用场景

        Fork/Join 框架主要适用于 计算密集型任务,尤其是能够拆分为多个独立子任务的场景,例如:
(1) 递归计算:如斐波那契数列、阶乘计算。
(2) 大规模数据处理:如数组求和、并行排序。
(3) 图像处理:如并行图像渲染、视频编码。
(4) 复杂递归算法优化:如深度优先搜索(DFS)。

(2) 核心组件

        Fork/Join 框架的核心组成部分包括:
(1) ForkJoinPool

  • 负责管理 Fork/Join 任务的线程池,提供任务调度功能。

(2) ForkJoinTask

  • 抽象任务类,定义了 fork()join() 方法。
  • 主要有两种子类:
    • RecursiveTask:有返回值的任务(如数组求和)。
    • RecursiveAction:无返回值的任务(如遍历操作)。
2 Fork/Join 的基本使用模式
(1) Fork/Join 的工作流程

(1) 任务拆分:调用 fork() 方法将任务拆分为多个子任务,并提交到线程池。
(2) 任务并行执行:子任务被多个线程执行。
(3) 任务合并:所有子任务完成后,调用 join() 方法合并结果。

(2) Fork/Join 代码示例
  • 示例:Fork/Join 并行计算数组求和
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.ForkJoinPool;

// 任务类:继承 RecursiveTask,进行数组求和
class SumTask extends RecursiveTask<Long> {
    private static final int THRESHOLD = 10; // 阈值,小于10时直接计算
    private int[] arr;
    private int start, end;

    public SumTask(int[] arr, int start, int end) {
        this.arr = arr;
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
        if ((end - start) <= THRESHOLD) { // 小任务直接计算
            long sum = 0;
            for (int i = start; i < end; i++) sum += arr[i];
            return sum;
        }

        // 拆分任务
        int mid = (start + end) / 2;
        SumTask leftTask = new SumTask(arr, start, mid);
        SumTask rightTask = new SumTask(arr, mid, end);

        // 并行执行
        leftTask.fork();
        long rightResult = rightTask.compute();
        long leftResult = leftTask.join();

        return leftResult + rightResult;
    }
}

// 测试代码
public class ForkJoinExample {
    public static void main(String[] args) {
        int[] arr = new int[1000];
        for (int i = 0; i < arr.length; i++) arr[i] = i;

        ForkJoinPool pool = new ForkJoinPool();
        SumTask task = new SumTask(arr, 0, arr.length);
        long result = pool.invoke(task);

        System.out.println("Sum: " + result);
    }
}
  • Fork/Join 代码解析

(1) 定义任务:继承 RecursiveTask<Long>,实现 compute() 方法。
(2) 任务拆分:当任务数据量大于 THRESHOLD 时,拆分为两个子任务。
(3) 并行执行:子任务分别 fork()compute() 计算,并 join() 汇总结果。
(4) 使用 ForkJoinPool 执行任务

3 Fork/Join 框架的底层实现原理
(1) 工作窃取(Work Stealing)算法

        Fork/Join 采用 工作窃取算法(Work Stealing Algorithm) 提高执行效率。
(1) 每个线程 维护一个 双端队列(Deque) 存储任务。
(2) 线程优先执行自己队列的任务
(3) 当线程空闲时,从其他线程的队列 尾部 窃取任务执行,减少线程空闲时间。

特点

  • 避免频繁创建/销毁线程,提升性能。
  • 负载均衡,防止某些线程过载,其他线程空闲。
(2) Fork/Join 任务调度机制

(1) 任务拆分(Fork):主线程将任务拆分成多个子任务,放入双端队列。
(2) 任务执行:工作线程从自己的队列 头部 取任务执行。
(3) 任务窃取:空闲线程从其他线程 尾部 窃取任务。
(4) 任务合并(Join):子任务完成后,结果汇总到主任务。

(3) ForkJoinPool 线程池结构

        ForkJoinPool 作为 Fork/Join 任务调度的核心,采用 分层线程管理
(1) 主线程池:管理所有 Worker 线程。
(2) Worker 线程:每个线程维护 一个双端队列,存储任务。
(3) 任务窃取:Worker 线程会从其他线程的队列 尾部 窃取任务,均衡负载。

4 Fork/Join 和 线程池对比
(1) Fork/Join 与 ThreadPoolExecutor 区别
特性Fork/Join线程池(ThreadPoolExecutor)
任务拆分任务递归拆分任务独立提交
任务执行任务间并行,窃取任务线程池分配任务
任务合并join() 合并结果任务间无直接通信
适用场景计算密集型任务IO密集型任务
性能更高效(减少线程切换)适用于独立任务
(2) 适用场景对比

(1) Fork/Join 适用于

  • 计算密集型任务(如数组求和、大数据计算)。
  • 递归计算(如斐波那契数列)。
  • 分治算法(如归并排序、快速排序)。

(2) 线程池适用于

  • IO 密集型任务(如数据库操作、网络请求)。
  • 并行执行多个独立任务(如 Web 服务器处理多个 HTTP 请求)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

GawynKing

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

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

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

打赏作者

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

抵扣说明:

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

余额充值