并发编程/6种线程池设计图/1大线程池标准设计与执行规范/2种线程池管理设计(全面篇)

开发 前端
在Java 5之前,开发者需要手动管理线程的创建和销毁,这不仅增加了编程复杂性,还可能导致资源浪费和系统开销。 ExecutorService 通过提供线程池管理功能,简化了线程的生命周期管理。

在现代多核处理器时代,线程池成为了并发编程中不可或缺的工具,它不仅提高了程序性能,还简化了线程管理。线程池允许我们重用有限数量的线程来执行大量任务,从而减少了线程创建和销毁的开销。Java中的 ExecutorService接口及其实现类,如 FixedThreadPool、 SingleThreadExecutor、 CachedThreadPool和 ScheduledThreadPool,提供了强大的线程池管理功能。这些线程池通过智能地调度任务和复用线程,帮助我们优化资源利用,提高响应速度,并处理复杂的并发场景。对于Java开发者而言,理解线程池的工作原理和正确选择适当的线程池类型对于构建高效、可伸缩的并发应用至关重要。

1、线程池工作流程

图片图片

  1. ExecutorService:这是线程池的管理接口,负责提交任务和管理工作线程。
  2. 任务队列(Task Queue) :这是一个先进先出(FIFO)的队列,用于存储待执行的任务。
  3. 线程池(Thread Pool) :这是一组工作线程的集合,它们从任务队列中取出任务并执行。
  4. 工作线程(Worker Thread) :线程池中的每个线程都会循环地从任务队列中取出任务并执行。
  5. 任务(Task) :这是需要执行的具体任务,可以是 Runnable 或 Callable 对象。
  6. 返回结果(Return Result) :任务执行完成后,会返回结果或异常信息。

2、 ExecutorService 设计本质

  1. 线程生命周期管理:

在Java 5之前,开发者需要手动管理线程的创建和销毁,这不仅增加了编程复杂性,还可能导致资源浪费和系统开销。 ExecutorService 通过提供线程池管理功能,简化了线程的生命周期管理。

  1. 系统开销降低:

频繁地创建和销毁线程会导致性能问题和资源消耗。 ExecutorService 允许线程池重用线程,从而降低了系统开销。

  1. 资源利用率提升:

通过线程池复用线程, ExecutorService 提高了资源利用率和程序响应速度,使得多线程编程更加灵活和高效。

  1. 丰富的任务调度和并发控制:

ExecutorService 提供了丰富的任务调度和并发控制能力,使得多线程编程更加灵活和高效。

  1. 硬件发展推动:

随着多核架构的出现,Java的设计者们决定重新修订Java的内存模型,并在JDK1.5中引入了 java.util.concurrent包,其中就包括了 ExecutorService接口,以支持更高效的并行计算。

  1. 简化并发编程:

ExecutorService 作为Java并发编程的重要工具,简化了并发编程的复杂性,使得开发者可以更容易地实现并行处理和任务调度。

  1. 提高程序性能:

ExecutorService 通过减少线程创建和销毁的开销,提高了程序的性能和可伸缩性。

  1. 线程池的易用性:

Executors 类提供了便捷的工厂方法来创建不同类型的线程池,使得开发者无需手动实现复杂的线程池逻辑,就可以方便地使用线程池。

3、线程池类设计

图片图片

在这个类设计图中,我们有以下组件:

  • ExecutorService:这是一个接口,定义了线程池管理的方法,如 submit、 invokeAll、 invokeAny、 shutdown 等。
  • ThreadPoolExecutor:这是 ExecutorService 的一个具体实现,提供了线程池的详细控制,如 execute、 submit、 shutdown 等。
  • ScheduledExecutorService:这是 ExecutorService 的一个子接口,用于延迟执行或定期执行任务。
  • FutureTask:这是 Future 接口的一个实现类,用于封装异步任务,并提供方法如 run、 get、 isDone 等。

4、线程池功能范围设计

4.1. 接口定义

  • ExecutorService 扩展了 Executor 接口,增加了提交任务后返回 Future 对象的方法,这些方法允许任务异步执行,并提供了获取任务结果的机制。

4.2. 任务提交

  • submit(Callable<T>task): 提交一个返回结果的任务,并返回一个 Future 对象。
  • submit(Runnabletask): 提交一个不返回结果的任务,并返回一个 Future 对象。
  • submit(Runnabletask,T result): 提交一个不返回结果的任务,并返回一个已经设置好结果的 Future 对象。

4.3. 批量任务执行

  • invokeAll(Collection<?extendsCallable<T>>tasks): 提交一个任务集合,等待所有任务完成,并返回每个任务结果的列表。
  • invokeAny(Collection<?extendsCallable<T>>tasks): 提交一个任务集合,等待任意一个任务完成,并返回该任务的结果。

4.4. 线程池管理

  • shutdown(): 启动一次有序的关闭,执行已提交的任务,不接受新任务。
  • shutdownNow(): 尝试停止所有正在执行的任务,并返回未执行任务的列表。
  • awaitTermination(longtimeout,TimeUnitunit): 等待直到所有任务完成或超时。

4.5. 线程生命周期

ExecutorService 允许线程的复用,减少了线程创建和销毁的开销。线程池可以根据需要创建新线程或重用空闲线程。

4.6. 线程池的可扩展性

ExecutorService 可以与不同的线程池实现一起工作,如 FixedThreadPool、 CachedThreadPool、 ScheduledThreadPool 等,提供了高度的可扩展性和灵活性。

4.7. 异常处理

ExecutorService 提交的任务如果抛出异常,可以通过 Future 对象的 get 方法捕获这些异常。

4.8. 结果处理

Future 对象提供了 get 方法来获取任务结果,如果任务尚未完成, get 方法会阻塞直到任务完成。

4.9. 任务取消

Future 对象提供了 cancel 方法来取消任务,可以传入一个布尔值参数来决定是否中断正在执行的任务。

4.10. 线程工厂和拒绝策略

ExecutorService 可以使用自定义的线程工厂来创建线程,以及自定义的拒绝策略来处理任务提交过多时的情况。 ExecutorService 的设计提供了一个强大的框架,用于构建并发应用程序,它简化了并发编程的复杂性,同时提供了丰富的控制和灵活的配置选项。通过 ExecutorService,开发者可以更容易地实现线程安全的异步任务执行。

5、线程池的种类

  • FixedThreadPool:

拥有固定数量线程的线程池,适用于负载较重的服务器。 

图片图片

  • SingleThreadExecutor:

只有一个线程的线程池,用于顺序执行任务。 图片

  • CachedThreadPool:

根据需要创建新线程的线程池,对于短生命周期的异步任务非常合适。 图片

  • ScheduledThreadPool:

用于延迟执行或定期执行任务的线程池。 图片

  • SingleThreadScheduledExecutor:

单个线程的变体,用于延迟或定时执行任务。 图片

  • WorkStealingPool:

基于工作窃取算法的线程池,适用于并行计算。 

图片图片

这些线程池都是通过 Executors工具类提供的工厂方法来创建的。除了这些,开发者还可以通过直接实例化 ThreadPoolExecutor类来创建自定义配置的线程池。

ExecutorService接口本身并不定义线程池的具体实现,而是提供了一组通用的接口,用于管理和执行异步任务。不同的线程池实现提供了不同的功能和性能特性,以适应不同的并发场景。

6、 ThreadPoolExecutor 线程池设计

简化版 

图片图片

  • 核心参数初始化:包括核心线程数、最大线程数、任务队列、空闲线程存活时间和线程工厂等参数的初始化。
  • 任务提交到线程池:当任务被提交到线程池时,线程池会根据当前的状态和参数来决定如何处理这个任务。
  • 线程获取任务并执行:如果有空闲的核心线程,它会直接执行任务;如果没有空闲的核心线程但任务队列未满,任务会被添加到任务队列中。
  • 创建非核心线程执行任务:如果任务队列已满且当前线程数小于最大线程数,会创建一个新的非核心线程来执行任务。
  • 拒绝策略处理任务:如果任务队列已满且线程数达到最大线程数,任务将被拒绝,线程池会根据拒绝策略处理器来处理这个任务。
  • 线程池状态管理: ThreadPoolExecutor 维护一个 ctl 变量,用于控制线程池的状态,包括 RUNNING、SHUTDOWN、STOP、TIDYING 和 TERMINATED 等状态。

详细版 

图片图片

  • 创建 ThreadPoolExecutor:创建一个 ThreadPoolExecutor 实例,开始线程池的初始化过程。
  • 核心参数初始化:初始化线程池的核心参数,包括核心线程数、最大线程数、任务队列、空闲线程存活时间和线程工厂。
  • 任务提交到线程池:当任务被提交到线程池时,线程池会根据当前的状态和参数来决定如何处理这个任务。
  • 线程获取任务并执行:如果有空闲的核心线程,它会直接执行任务;如果没有空闲的核心线程但任务队列未满,任务会被添加到任务队列中。
  • 创建非核心线程执行任务:如果任务队列已满且当前线程数小于最大线程数,会创建一个新的非核心线程来执行任务。
  • 拒绝策略处理任务:如果任务队列已满且线程数达到最大线程数,任务将被拒绝,线程池会根据拒绝策略处理器来处理这个任务。
  • 线程尝试获取新任务:任务执行完毕后,线程会尝试从任务队列中获取新的任务。
  • 线程销毁或等待新任务:如果任务队列空,线程会进入空闲状态,如果达到空闲线程存活时间,线程将被销毁。
  • 线程池状态检查:线程池会根据其状态来决定是否停止接收新任务,是否中断运行中的任务,以及是否等待线程终止。

6.1 ThreadPoolExecutor应用

6.1.1. 服务器端处理请求

在服务器应用中, ThreadPoolExecutor 可以用来处理客户端的请求。服务器可以创建一个固定大小的线程池来同时处理多个请求,提高响应速度和吞吐量。

int corePoolSize = 10; // 核心线程数
int maximumPoolSize = 50; // 最大线程数
long keepAliveTime = 120; // 空闲线程存活时间
TimeUnit unit = TimeUnit.SECONDS;
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(100); // 任务队列

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    corePoolSize,
    maximumPoolSize,
    keepAliveTime,
    unit,
    workQueue
);

// 提交任务到线程池
executor.execute(new ClientRequestHandler());
6.1.2. 批量数据处理

在处理批量数据时,如文件处理或数据库批量操作, ThreadPoolExecutor 可以用来并行处理数据,提高处理速度。

List<Data> dataList = ...; // 待处理的数据列表
int threadCount = Runtime.getRuntime().availableProcessors(); // 使用可用处理器数量作为线程数

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    threadCount,
    threadCount,
    0L,
    TimeUnit.MILLISECONDS,
    new LinkedBlockingQueue<>()
);

dataList.forEach(data -> executor.execute(new DataProcessorTask(data)));
6.1.3. 异步任务执行

在需要异步执行任务的场景中, ThreadPoolExecutor 可以用来提交任务并在未来某个时刻获取结果。

Future<String> future = executor.submit(() -> {
    // 异步执行的任务
    return "任务结果";
});

// 获取异步任务的结果
String result = future.get();
6.1.4. 定时和周期性任务

ThreadPoolExecutor 可以与 ScheduledExecutorService 结合使用,来执行定时和周期性任务。

ScheduledExecutorService scheduledExecutor = new ScheduledThreadPoolExecutor(1);

scheduledExecutor.scheduleAtFixedRate(() -> {
    // 定时执行的任务
}, 0, 10, TimeUnit.SECONDS);
6.1.5. 资源受限环境下的任务处理

在资源受限的环境中,如移动设备或嵌入式系统, ThreadPoolExecutor 可以用来合理分配有限的计算资源。

int maxThreads = 4; // 根据设备性能设置最大线程数

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    maxThreads,
    maxThreads,
    60L,
    TimeUnit.SECONDS,
    new SynchronousQueue<>()
);

7、 ScheduledExecutorService 线程池设计

图片图片

  • ScheduledExecutorService:这是线程池的管理接口,负责提交和管理任务。
  • 任务队列(Task Queue) :这是一个延迟队列,用于存储待执行的任务,按照预定的执行时间排序。
  • 核心线程池(Core Thread Pool) :线程池中的核心线程会不断地从任务队列中取出任务并执行。
  • 工作线程(Worker Thread) :线程池中的线程负责执行任务。
  • 执行周期性任务(scheduleAtFixedRate) :用于安排任务以固定频率执行。
  • 执行延迟任务(scheduleWithFixedDelay) :用于安排任务在每次执行完毕后按照固定延迟执行。
  • 执行单次任务(schedule) :用于安排任务在指定延迟后执行一次。
  • 任务完成:任务执行完毕后,如果是周期性任务,会重新调度下一次执行。
  • 线程池关闭(Thread Pool Shutdown) :当不再需要线程池时,可以关闭线程池,等待所有任务完成或尝试立即停止所有任务。

7.1 ScheduledExecutorService应用案例

7.1.1. 一次性延迟执行任务

在这个例子中,我们创建了一个 ScheduledExecutorService 实例,并安排了一个任务在延迟一定时间后执行。

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class DelayedTaskExample {
    public static void main(String[] args) {
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
        Runnable task = () -> System.out.println("任务在延迟后执行:" + System.currentTimeMillis());
        scheduler.schedule(task, 5, TimeUnit.SECONDS); // 5秒后执行
        scheduler.shutdown(); // 执行完毕后关闭调度器
    }
}
7.1.2. 固定速率周期执行任务

在这个例子中,我们安排了一个任务以固定的速率周期性执行。

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class FixedRateTaskExample {
    public static void main(String[] args) {
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
        Runnable task = () -> System.out.println("定期任务执行时间: " + System.currentTimeMillis());
        scheduler.scheduleAtFixedRate(task, 0, 10, TimeUnit.SECONDS); // 每10秒执行一次
        scheduler.shutdown(); // 执行完毕后关闭调度器
    }
}
7.1.3. 固定延迟周期执行任务

在这个例子中,我们安排了一个任务在每次执行完毕后,等待固定的延迟时间再执行下一次。

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class FixedDelayTaskExample {
    public static void main(String[] args) {
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
        Runnable task = () -> System.out.println("带有固定延迟的任务执行时间: " + System.currentTimeMillis());
        scheduler.scheduleWithFixedDelay(task, 0, 15, TimeUnit.SECONDS); // 每次执行完毕后等待15秒再执行
        scheduler.shutdown(); // 执行完毕后关闭调度器
    }
}

责任编辑:武晓燕 来源: Solomon肖哥弹架构
相关推荐

2020-10-19 10:01:12

Nodejs线程池设计

2017-01-10 13:39:57

Python线程池进程池

2013-05-23 15:59:00

线程池

2022-11-09 09:01:08

并发编程线程池

2020-09-04 10:29:47

Java线程池并发

2023-06-07 13:49:00

多线程编程C#

2024-07-15 08:20:24

2023-05-19 08:01:24

Key消费场景

2023-07-05 07:48:04

线程池join关闭状态

2023-07-11 08:34:25

参数流程类型

2024-10-06 14:37:52

2024-10-16 10:11:52

2020-12-08 08:53:53

编程ThreadPoolE线程池

2021-12-03 09:04:53

线程设计进程池

2020-07-08 12:05:55

Java线程池策略

2024-11-11 17:39:01

2012-05-15 02:18:31

Java线程池

2020-12-10 08:24:40

线程池线程方法

2023-10-13 08:20:02

Spring线程池id

2023-11-29 16:38:12

线程池阻塞队列开发
点赞
收藏

51CTO技术栈公众号