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

举报
肖哥弹架构 发表于 2024/11/26 13:05:29 2024/11/26
【摘要】 在现代多核处理器时代,线程池成为了并发编程中不可或缺的工具,它不仅提高了程序性能,还简化了线程管理。线程池允许我们重用有限数量的线程来执行大量任务,从而减少了线程创建和销毁的开销。Java中的ExecutorService接口及其实现类,如FixedThreadPool、SingleThreadExecutor、CachedThreadPool和ScheduledThreadPool,提供了强大的

image.png

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

1、线程池工作流程

image.png

  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 通过提供线程池管理功能,简化了线程的生命周期管理。
  2. 系统开销降低:
    • 频繁地创建和销毁线程会导致性能问题和资源消耗。ExecutorService 允许线程池重用线程,从而降低了系统开销。
  3. 资源利用率提升:
    • 通过线程池复用线程,ExecutorService 提高了资源利用率和程序响应速度,使得多线程编程更加灵活和高效。
  4. 丰富的任务调度和并发控制:
    • ExecutorService 提供了丰富的任务调度和并发控制能力,使得多线程编程更加灵活和高效。
  5. 硬件发展推动:
    • 随着多核架构的出现,Java的设计者们决定重新修订Java的内存模型,并在JDK1.5中引入了java.util.concurrent包,其中就包括了ExecutorService接口,以支持更高效的并行计算。
  6. 简化并发编程:
    • ExecutorService 作为Java并发编程的重要工具,简化了并发编程的复杂性,使得开发者可以更容易地实现并行处理和任务调度。
  7. 提高程序性能:
    • ExecutorService 通过减少线程创建和销毁的开销,提高了程序的性能和可伸缩性。
  8. 线程池的易用性:
    • Executors 类提供了便捷的工厂方法来创建不同类型的线程池,使得开发者无需手动实现复杂的线程池逻辑,就可以方便地使用线程池。

3、线程池类设计

image.png

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

  • 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(Runnable task): 提交一个不返回结果的任务,并返回一个 Future 对象。
  • submit(Runnable task, T result): 提交一个不返回结果的任务,并返回一个已经设置好结果的 Future 对象。

4.3. 批量任务执行

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

4.4. 线程池管理

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

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、线程池的种类

  1. FixedThreadPool:

    • 拥有固定数量线程的线程池,适用于负载较重的服务器。
      image.png
  2. SingleThreadExecutor:

    • 只有一个线程的线程池,用于顺序执行任务。
      image.png
  3. CachedThreadPool:

    • 根据需要创建新线程的线程池,对于短生命周期的异步任务非常合适。
      image.png
  4. ScheduledThreadPool:

    • 用于延迟执行或定期执行任务的线程池。
      image.png
  5. SingleThreadScheduledExecutor:

    • 单个线程的变体,用于延迟或定时执行任务。
      image.png
  6. WorkStealingPool:

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

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

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

6、ThreadPoolExecutor 线程池设计

简化版
image.png

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

详细版
image.png

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

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 线程池设计

image.png

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

7.1ScheduledExecutorService应用案例

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(); // 执行完毕后关闭调度器
    }
}
【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。