线程池详细说明

一、概念

1.1、什么是线程池?

线程池(Thread Pool)是一种复用线程资源的机制,通过预先创建一定数量的线程,并管理它们的生命周期,避免频繁创建/销毁线程带来的性能开销。
线程池的核心思想是任务提交与执行解耦:你只管提交任务,线程池负责调度和执行。

(1)本质

提前创建好线程,需要用的时候直接从池子里拿出来用,用完了也不要释放而是返还回池子中。

(2)优缺点

①优点:节省了创建和销毁线程带来的资源消耗,更高效

②缺点:占用了内存空间

(3)高效的原因

从线程池里获取线程,是在用户态代码中进行调度,是可控的,高效的

从操作系统中获取线程,是在系统内核中进行完成的,不可控,低效。

(4)作用

  1. 提升性能:线程池能独立负责线程的创建、维护和分配,重复利用已创建的线程,节省了频繁创建和销毁带来的性能损耗,任务来了分配一个线程就可以立即干活,而不用等待线程重新创建。
  2. 线程管理:每个Java线程池会保持一些基本的线程统计信息,对线程进行有效管理,线程池控制线程数,避免过多消耗服务器资源,亦更方便调优和监控。

(5)执行流程

举例:

某银行柜台,共有5个窗口(Maximum Pool Size),平时常开2个窗口办理业务(Core Pool Size),银行大厅摆了5个椅子(Work Queue)供客户等待。银行规定当常开的窗口都在办理业务,并且大厅椅子上都坐满了客户,那么另外3个不常开的窗口也要打开办理业务。如果这3个窗口也都全部在办理业务,后面继续来银行办理业务的客户银行将拒绝办理。如果某个员工空闲下并且超过了5(Keep Alive Time)秒钟(TimeUnit)那么他就可以关闭窗口去休息。但是必须保留2个常开的窗口。

1.2、为什么要线程池?

  1. 线程创建/销毁成本高
    每次创建和销毁线程会消耗系统资源(如内存、CPU),频繁操作会导致性能下降。线程池通过复用已创建的线程,减少开销。
  2. 避免资源耗尽风险
    无限制创建线程可能导致内存溢出(OOM)或CPU过载。线程池通过限制最大线程数,防止系统崩溃。
  3. 提升响应速度
    任务到达时,线程池中可能有空闲线程立即执行,无需等待线程创建。
  4. 统一管理任务队列
    线程池提供任务队列缓冲突发流量,并支持多种拒绝策略(如丢弃任务或抛异常),避免任务丢失或系统过载。

1.3、线程池为何比系统直接创建线程更高效?

从线程池拿线程是纯粹的用户态操作;而从系统创建线程,就涉及到用户态和内核态之间的切换(真正的创建是要在内核态完成的)。

详细说明

线程池获取线程属于纯粹的用户态操作。线程池预先创建并维护一定数量的线程,当有任务需要执行时,直接从线程池内复用空闲线程,避免了复杂的系统资源分配流程。这种方式仅在用户程序空间内完成调度,无需与操作系统内核进行频繁交互,因此执行速度快、开销小。

相比之下,系统创建线程需要经历用户态与内核态的切换过程。操作系统中,内核作为核心功能模块的集合,承担着硬件管理、进程调度、内存分配等关键任务。应用程序若需创建新线程,必须通过系统调用向内核发起请求,由内核完成线程的实际创建工作(如分配内存、初始化线程上下文等)。这一过程中,程序执行状态需在用户态(应用程序运行空间)与内核态(操作系统核心空间)之间切换,涉及大量的上下文保存与恢复操作,同时还需等待内核调度资源,产生较高的时间和资源开销。

举例

到大厅办理业务,柜台内部就相当于内核态,而大厅相当于用户态

需要复印身份证

1、如果自己去复印,快去快回,复印完之后立即就回来了。

2、如果由工作人员复印,他可能去复印的同时还顺便干点别的,比如喝口水,上个厕所,完成一下上级给的别的任务,和同事唠唠嗑……最终确实也能完成复印,但可能就没那么及时了。

纯用户态的操作,时间是可控的;但涉及到内核态操作,时间就不太可控了。

用户态与内核态

在操作系统的运行体系中,用户态(User Mode内核态(Kernel Mode)是保障系统安全与高效运行的核心概念,它们基于处理器的特权级机制,对不同程序的资源访问权限进行严格划分。

用户态与内核态的概念

  • 用户态:是应用程序运行的状态。处于用户态的程序仅拥有有限的系统资源访问权限,只能执行非特权指令,如普通的算术运算、逻辑判断等。应用程序无法直接操作硬件设备、修改内存映射或调度其他进程,这一限制有效避免了因程序错误或恶意行为导致的系统崩溃,保障了系统稳定性。例如,用户编写的 Java 程序、浏览器、办公软件等均在用户态下运行。
  • 内核态:是操作系统内核运行的状态,具备最高的系统权限。内核能够执行所有特权指令,直接管理硬件资源(如 CPU、内存、磁盘、网卡等),控制进程的创建与销毁,管理文件系统,以及协调多个应用程序之间的资源分配。当操作系统执行关键任务,如进程调度、内存分配、设备驱动程序调用时,会切换至内核态完成操作。

两者的关系与交互

操作系统本质上由内核配套的应用程序组成。内核作为系统的核心枢纽,集成了硬件管理、进程调度、内存分配、文件系统等关键功能模块,为上层应用程序提供基础支持。例如,当应用程序执行println("hello")打印操作时:

  1. 应用程序首先在用户态发起系统调用请求;
  2. 操作系统通过中断机制将程序执行状态切换至内核态;
  3. 内核接收到请求后,调用相应的驱动程序控制显示器硬件,完成字符串的输出;
  4. 操作完成后,内核将执行状态切回用户态,应用程序继续执行后续逻辑。

内核资源调度的挑战

由于同一时刻运行的应用程序数量众多,而内核资源(如 CPU 时间片、内存带宽、文件描述符等)有限,且内核实例仅有一个,这使得内核在处理大量并发请求时面临调度压力。当多个应用程序同时请求内核服务(如创建线程、读写文件、分配内存),内核需要通过复杂的调度算法(如时间片轮转、优先级调度)来分配资源,这可能导致部分请求出现响应延迟。例如,在高并发场景下,频繁的系统调用会加剧内核态与用户态之间的切换开销,降低整体系统性能。

通过区分用户态与内核态,操作系统既能保护内核关键资源不被非法访问,又能实现对应用程序的高效管理,为计算机系统的稳定运行提供了坚实保障。

1.4、线程池解决了哪些问题

问题

线程池的解决方案

频繁创建/销毁线程开销大

线程复用,减少系统调用和资源消耗

线程数量不可控

通过核心线程数、最大线程数等参数限制并发量

任务执行缺乏管理

提供任务队列、拒绝策略、监控接口等统一管理机制

系统稳定性差

避免资源耗尽,防止OOM或CPU过载

1.5、线程池的适用场景

  1. 高并发请求处理
    • Web服务器(如Tomcat):每个HTTP请求分配一个线程处理,线程池管理这些线程,避免瞬间流量压垮系统。
    • RPC框架(如Dubbo):远程调用通过线程池异步处理,提升吞吐量。
  1. 异步任务处理
    • 后台日志记录、数据清洗等非实时任务,提交到线程池异步执行,不阻塞主线程。
  1. 定时/周期性任务
    • 使用ScheduledThreadPool执行定时任务(如每日报表生成),或周期任务(如心跳检测)。
  1. 资源受限场景
    • 当系统资源(如数据库连接)有限时,通过线程池控制并发访问数量,防止资源争用。

1.6、线程池架构

1.Executor

Executor 是 Java 并发包中的一个基础接口,其主要目的是将任务的提交和任务的执行进行解耦。开发人员使用 Executor 时,只需将任务(通常是 Runnable 实例)提交给 Executor,而无需手动管理线程的创建、启动和销毁等复杂操作。线程的创建、管理和调度由 Executor 的具体实现类负责,这样可以提高代码的可维护性和性能,避免手动管理线程带来的复杂性和潜在问题。

Executor 接口仅定义了一个方法 void execute(Runnable command),该方法的作用是执行提交的 Runnable 任务。当调用 execute 方法并传入一个 Runnable 实例时,Executor 的具体实现会根据自身的策略来安排该任务的执行,比如可能会立即执行、放入队列等待执行或者创建新线程来执行等。

2.ExecutorService(Java 异步任务管理的核心接口)

一、基本概念

ExecutorService 是 Java 中用于管理和执行异步任务的核心接口,继承自 Executor 接口,提供了更丰富的线程池管理与任务控制功能。通过该接口,开发者无需手动创建和销毁线程,只需将任务提交给线程池,由其自动管理线程的创建、复用和销毁,显著提升多线程任务的执行效率与可控性。

二、核心功能与方法
1. 任务提交与执行
  • execute(Runnable task):直接执行一个无返回值的 Runnable 任务,适用于无需结果的异步操作。
  • submit 系列方法:提交任务并返回 Future 对象,用于获取任务结果或控制任务状态:
    • Future<T> submit(Callable<T> task):提交有返回值的 Callable 任务,Future.get() 可获取执行结果。
    • Future<T> submit(Runnable task, T result):提交 Runnable 任务并指定一个自定义结果,任务完成后 Future.get() 返回该结果。
    • Future<?> submit(Runnable task):提交无返回值的 Runnable 任务,Future.get() 返回 null,但可通过 isDone()cancel() 等方法监控任务状态。
2. 线程池生命周期控制
  • shutdown():优雅关闭线程池,不再接受新任务,但会等待已提交的任务(包括队列中未执行的任务)执行完毕,随后销毁线程池。
  • shutdownNow():立即关闭线程池,尝试中断正在执行的任务,取消所有未开始的任务,并返回未执行的任务列表。
3. 批量任务处理
  • invokeAll(Collection<? extends Callable<T>> tasks):提交一组 Callable 任务,等待所有任务完成后返回结果列表。
  • invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit):设置超时时间,超过时间后未完成的任务会被取消。
  • invokeAny(Collection<? extends Callable<T>> tasks):提交一组 Callable 任务,只要其中一个任务成功完成,立即返回其结果,其他任务会被取消。
4. 异步任务与结果获取
  • 异步特性:任务提交后无需阻塞等待,主线程可继续执行其他逻辑。通过 Future 对象可实现:
    • get():阻塞等待任务完成并获取结果(支持超时参数)。
    • isDone():判断任务是否完成。
    • cancel(boolean mayInterruptIfRunning):尝试取消任务,mayInterruptIfRunning 表示是否中断正在执行的任务。
三、核心优势与适用场景
  • 资源复用:避免频繁创建 / 销毁线程的开销,通过线程池复用线程,提升性能。
  • 可控性:通过 shutdown()/shutdownNow() 精确控制线程池关闭逻辑,避免任务丢失或资源泄漏。
  • 异步编程支持:完美适配耗时操作(如网络请求、IO 处理),通过 Future 实现非阻塞结果获取,提升系统吞吐量。
四、关键源码与设计
// 提交单个异步任务(Callable 有返回值)  
<T> Future<T> submit(Callable<T> task);  

// 提交单个异步任务(Runnable 无返回值,指定自定义结果)  
<T> Future<T> submit(Runnable task, T result);  

// 提交批量异步任务,等待所有任务完成并返回结果列表  
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;

通过以上功能,ExecutorService 成为 Java 并发编程中管理线程池与异步任务的标准工具,广泛应用于高并发场景,如电商订单处理、大数据并行计算、异步日志服务等,有效平衡性能、资源利用率与代码可维护性。

3. AbstractExecutorService

AbstractExecutorService 是 Java 中的一个抽象类,它是 ExecutorService 接口的一个实现。AbstractExecutorService 提供了一些默认实现,以简化 ExecutorService 的实现要实现很多方法的问题。

4. ThreadPoolExecutor

ThreadPoolExecutor 是 Java 中的一个类,它是 AbstractExecutorService 的一个实现,提供了线程池的功能。

ThreadPoolExecutor 允许你配置线程池的各种参数,通过构造函数,可以设置线程池的核心线程数、最大线程数、线程存活时间等参数。

这些参数可以根据实际需求进行调整,以达到最佳的性能表现。

5. ScheduledExecutorService

ScheduledExecutorService是专门用于调度任务的接口,它扩展了ExecutorService的功能,增加了定时任务相关的功能。

通过ScheduledExecutorService,可以安排任务在给定的延迟后执行,或者定期执行,适用于需要定时或周期性执行的任务。

6. ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor是ThreadPoolExecutor的子类,它实现ScheduledExecutorService接口,因此提供了线程池和调度器的组合。

7. Executors

Executors是Java中用于创建线程池的工厂类,位于java.util.concurrent包下。Executors提供了几个静态方法来创建不同类型的线程池,如固定线程数的线程池、单线程线程池、可缓存线程池、定时任务线程池。

二、使用线程池

1、线程池的构造方法

public ThreadPoolExecutor(
    int corePoolSize,//核心线程数,也就是线程池始终保持存活的线程数量
    int maximumPoolSize,//最大线程数,即线程池允许创建的最大线程数量
    long keepAliveTime,//空闲线程的存活时间,超过这个时间,空闲线程会被销毁
    TimeUnit unit,//时间单位
    BlockingQueue<Runnable> workQueue,//任务队列,用于存放待执行的任务
    ThreadFactory threadFactory,//线程工厂,用来创建线程,可用于自定义线程的创建方式
    RejectedExecutionHandler handler//拒绝策略,当任务队列已满且线程池达到最大线程数时,新提交的任务会按照该策略进行处理
) 
  1. corePoolSize:核心线程数
    初始时,线程池内没有线程,当有任务提交进来,首先创建的就是核心线程去执行任务。核心线程在一般情况下会一直存活在线程池中,即便它们处于闲置状态,但可通过设置 allowCoreThreadTimeOut(true) 使其在空闲时被销毁。
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    10, 20, 60, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>()
);
executor.allowCoreThreadTimeOut(true); // 核心线程闲置 60 秒后被销毁
  1. maximumPoolSize:最大线程数
    最大线程数由核心线程数与非核心线程数组成,即最大线程数 = 核心线程数 + 非核心线程数。只有当线程数达到核心线程数,并且任务缓存队列已满时,才会创建非核心线程来处理新任务。
  2. keepAliveTime:非核心线程存活时间
    它用于定义非核心线程在空闲状态下的存活时长。若非核心线程在这段时间内没有任务可执行,就会被销毁。通常,核心线程即便闲置也会保留在线程池中。不过,若将ThreadPoolExecutorallowCoreThreadTimeOut属性设置为true ,核心线程在闲置一段时间后同样会被销毁。
  3. TimeUnit unit:非核心线程存活时间单位
    该参数用于指定keepAliveTime的时间单位,例如SECONDS(秒)、MINUTES(分钟)等,明确非核心线程存活时间的度量尺度。
  4. workQueue:缓存队列
    • SynchronousQueue:此队列在接收到任务时,不会暂存任务,而是直接将任务提交给线程处理。若此时所有线程都处于忙碌状态,便会立即创建一个新线程来处理该任务。
    • LinkedBlockingQueue:当任务进入该队列时,若当前线程数小于核心线程数,会新建核心线程来处理任务;若当前线程数等于核心线程数,任务则进入队列等待。该队列没有容量上限,所有超出核心线程数的任务都会被添加到队列中,这会致使maximumPoolSize的设定失去作用,因为总线程数永远不会超过corePoolSize
    • ArrayBlockingQueue:任务入队时,若当前线程数小于核心线程数,会新建核心线程执行任务;若当前线程数等于核心线程数,任务进入队列等待;若队列已满,则新建非核心线程执行任务;当总线程数达到maximumPoolSize时,会触发拒绝策略。此队列可以限定长度。
    • DelayQueue:任务进入该队列后,会先入队等待。只有当达到指定的延时时间后,任务才会被执行。并且,队列内的元素(即任务)必须实现Delayed接口。
  1. threadFactory:线程工厂
  • 接口定义与功能ThreadFactory是一个接口,其中仅包含一个newThread(Runnable r)方法。通过实现该接口,我们能够对线程进行自定义初始化操作。比如,在创建线程时为其设定特定的名字,这在后期调试过程中,有助于快速准确地识别和追踪线程,极大地提高调试效率。java
public interface ThreadFactory {
    Thread newThread(Runnable r);
}
  • 创建线程时的操作:当调用ThreadFactory接口中的newThread()方法来创建新线程时,具备更改新线程多项属性的能力,包括但不限于线程名称、线程组、优先级以及守护进程状态等。借助这些操作,可以更灵活地控制线程的特性,以满足不同的业务需求。
  • 在创建线程池时的应用:在使用Executors工具类创建新的线程池时,存在两种情况。一种是可以显式指定ThreadFactory ,从而按照自定义的方式创建线程;另一种是未指定的情况,此时会采用默认的方式。需要明确区分的是,Executors是线程池工厂类,主要负责快捷创建线程池(Thread Pool) ;而ThreadFactory是线程工厂类,专注于创建线程(Thread) 。例如:java
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory);
public static ExecutorService newFixedThreadPool(int nThreads);
  1. RejectedExecutionHandler:拒绝策略

当提交的任务数量超过了maximumPoolSizeworkQueue容量之和时,线程池就会触发拒绝策略。以下是几种常见的拒绝策略以及自定义策略的相关说明:

  • AbortPolicy:这是线程池的默认拒绝策略。当触发该策略时,会直接抛出RejectedExecutionException异常,以此表明任务无法被当前线程池接收并处理。这种策略适用于希望明确感知任务提交失败,并及时进行错误处理和排查的场景。
  • DiscardPolicy:该策略会直接丢弃当前提交的任务,并且不会抛出任何异常。它适用于某些对任务完整性要求不高,或者允许部分任务丢失的场景,例如一些非关键的统计信息收集任务等。
  • DiscardOldestPolicy:此策略会丢弃任务队列中最早添加的元素,然后尝试将当前任务安排进入队列。若安排失败,则会不断进行重试。该策略在一定程度上保证了新任务的执行机会,同时也对队列中的旧任务进行了处理。
  • CallerRunsPolicy:采用该策略时,线程池会使用调用者自身的线程来执行任务。也就是说,调用执行器execute方法的那个线程会承担起执行该任务的职责。这种策略有助于降低新任务的提交速度,减轻线程池的压力,并且在一定程度上保证了任务最终能够被执行。
  • 自定义策略:通过实现RejectedExecutionHandler接口,并实现其中的rejectedExecution(Runnable r, ThreadPoolExecutor executor)方法,用户可以根据自身业务需求定制独特的拒绝策略。在该方法中,可以编写任意符合业务逻辑的处理代码,比如记录详细的日志信息、将任务持久化存储到外部介质以便后续重新处理等。

2、创建线程池

2.1、通过 Executors 工厂类

Executors 类提供了一些静态方法来创建不同类型的线程池,这些方法底层也是基于 ThreadPoolExecutor 实现的

// 单线程线程池(保证任务顺序执行)
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();

// 固定线程数线程池
ExecutorService fixedPool = Executors.newFixedThreadPool(5);

// 可缓存线程池(线程数自动伸缩)
ExecutorService cachedPool = Executors.newCachedThreadPool();

// 可调度线程池(定时任务线程池)
ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(3);

FixedThreadPool(固定大小线程池)和SingleThreadExecutor(单线程化线程池) 使用了 LinkedBlockingQueue作为任务队列 ,这是个无界队列。当任务突发过多时,这个队列可能因为缓存太多任务而消耗非常多的内存资源,最终导致OOM

CachedThreadPool(可缓存线程池)和ScheduledThreadPool(可调度线程池)最大线程数是Integer.MAX_VALUE。即相当于没有对最大线程数做限制,任务突发过多时,可能因为创建大量线程导致资源耗尽,最终同样导致OOM

1. newSingleThreadExecutor:创建 “单线程化线程池”
  • 特性:该线程池内只有一个线程,任务会按照提交的次序顺序执行,就像在一条单行道上依次通行的车辆。池中的唯一线程存活时间无限,若因异常结束,会有新线程替代。当此线程繁忙时,新提交任务进入内部无界的阻塞队列等待。
  • 适用场景:适用于任务需按顺序逐个执行,遵循先进先出(FIFO)原则的场景,比如一些对执行顺序严格要求的日志记录任务等。
  • 缺点:与固定数量线程池类似,由于阻塞队列无界,可能在任务量极大时占用过多内存。
  • 示例代码
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SingleThreadExecutorExample {
    private static final Logger log = LoggerFactory.getLogger(SingleThreadExecutorExample.class);

    public static void main(String[] args) {
        // 创建单线程化的线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        // 提交多个任务
        for (int i = 0; i < 5; i++) {
            final int taskId = i;
            executorService.submit(() -> {
                log.debug("Executing task " + taskId);
                try {
                    // 模拟任务执行时间
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    log.error("Task interrupted", e);
                }
                log.debug("Completed task " + taskId);
            });
        }

        // 关闭线程池
        executorService.shutdown();
        try {
            if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
                executorService.shutdownNow();
            }
        } catch (InterruptedException e) {
            executorService.shutdownNow();
            log.error("Termination interrupted", e);
        }
    }
}
2. newFixedThreadPool:创建 “固定数量的线程池”
  • 特性:在创建线程池时需指定固定数量的线程。初始时,若线程数未达指定数量,每提交一个任务就创建一个新线程,直至达到固定数量。此后线程池大小保持不变,若有线程因执行异常结束,会补充新线程。当所有线程都繁忙时,新任务进入无界的阻塞队列等待。
  • 适用场景:适合任务需长期执行的场景,尤其对于 CPU 密集型任务,能发挥较好性能。比如一些持续进行复杂计算的任务。
  • 缺点:使用无界队列存放排队任务,当任务量过大超过线程池处理能力时,队列会不断增大,可能迅速耗尽服务器资源,甚至引发 JVM 的 OOM(Out Of Memory,内存耗尽)异常。
  • 示例代码
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class FixedThreadPoolExample {
    private static final Logger log = LoggerFactory.getLogger(FixedThreadPoolExample.class);

    public static void main(String[] args) {
        // 创建固定数量的线程池,线程池中有3个线程
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        // 提交多个任务
        for (int i = 0; i < 10; i++) {
            final int taskId = i;
            executorService.submit(() -> {
                log.debug("Executing task " + taskId);
                try {
                    // 模拟任务执行时间
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    log.error("Task interrupted", e);
                }
                log.debug("Completed task " + taskId);
            });
        }

        // 关闭线程池
        executorService.shutdown();
        try {
            if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
                executorService.shutdownNow();
            }
        } catch (InterruptedException e) {
            executorService.shutdownNow();
            log.error("Termination interrupted", e);
        }
    }
}
3. newCachedThreadPool:创建 “可缓存线程池”
  • 特性:当接收新任务时,若池内所有线程都繁忙,会立即添加新线程处理任务。该线程池没有核心线程的概念,最大线程数为Integer.MAX_VALUE ,缓存队列不存储任务。若部分线程空闲(闲置 60 秒不执行任务),这些空闲线程会被回收。
  • 适用场景:适用于快速处理突发性强、耗时较短的任务场景,例如 Netty 的 NIO 处理场景、REST API 接口的瞬时削峰场景等,能快速响应并处理大量短时间任务。
  • 缺点:由于最大线程数不设限,若任务提交过多,可能创建大量线程,引发 OOM 异常,甚至耗尽 CPU 线程资源。
  • 示例代码
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CachedThreadPoolExample {
    private static final Logger log = LoggerFactory.getLogger(CachedThreadPoolExample.class);

    public static void main(String[] args) {
        // 创建可缓存线程池
        ExecutorService executorService = Executors.newCachedThreadPool();

        // 提交多个任务
        for (int i = 0; i < 10; i++) {
            final int taskId = i;
            executorService.submit(() -> {
                log.debug("Executing task " + taskId + " in thread " + Thread.currentThread().getName());
                try {
                    // 模拟任务执行时间
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    log.error("Task interrupted", e);
                }
                log.debug("Completed task " + taskId);
            });
        }

        // 关闭线程池
        executorService.shutdown();
        try {
            if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
                executorService.shutdownNow();
            }
        } catch (InterruptedException e) {
            executorService.shutdownNow();
            log.error("Termination interrupted", e);
        }
    }
}
4. newScheduledThreadPool:创建 “可调度线程池”
  • 特性:拥有固定数量的核心线程,最大线程数为Integer.MAX_VALUE,主要用于定时以及周期性执行任务。
  • 适用场景:适用于有定时任务需求的场景,如定时数据备份、定时发送邮件通知等,或者需要周期性执行某些任务的场景,像系统资源监控等。
  • 缺点:与可缓存线程池类似,线程数不设上限,可能因创建过多线程带来资源问题。
  • 示例代码
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ScheduledThreadPoolExample {
    private static final Logger log = LoggerFactory.getLogger(ScheduledThreadPoolExample.class);

    public static void main(String[] args) {
        // 创建可调度线程池,线程池中有3个核心线程
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);

        // 提交一个延迟任务,5秒后执行
        scheduledExecutorService.schedule(() -> {
            log.debug("Executing delayed task");
        }, 5, TimeUnit.SECONDS);

        // 提交一个周期性任务,初始延迟2秒后开始执行,每隔3秒执行一次
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            log.debug("Executing periodic task at fixed rate");
        }, 2, 3, TimeUnit.SECONDS);

        // 提交一个周期性任务,初始延迟2秒后开始执行,每次任务完成后间隔3秒再次执行
        scheduledExecutorService.scheduleWithFixedDelay(() -> {
            log.debug("Executing periodic task with fixed delay");
            try {
                // 模拟任务执行时间
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                log.error("Task interrupted", e);
            }
        }, 2, 3, TimeUnit.SECONDS);

        // 关闭线程池
        // 注意:这里没有立即关闭线程池,因为周期性任务会一直运行
        // 如果需要关闭线程池,请确保所有任务已经完成或取消
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            log.debug("Shutting down scheduled executor service");
            scheduledExecutorService.shutdown();
            try {
                if (!scheduledExecutorService.awaitTermination(60, TimeUnit.SECONDS)) {
                    scheduledExecutorService.shutdownNow();
                }
            } catch (InterruptedException e) {
                scheduledExecutorService.shutdownNow();
                log.error("Termination interrupted", e);
            }
        }));
    }
}

补充说明:虽然newFixedThreadPool适合 CPU 密集型任务,但对于 IO 密集型任务(如网络请求或数据库操作),线程可能会在等待 IO 操作完成时阻塞,导致线程资源浪费。这种情况下,可考虑使用newCachedThreadPoolnewSingleThreadExecutor

2.2、使用 ThreadPoolExecutor

可以自定义核心参数(核心线程数、队列类型、拒绝策略等)

ThreadPoolExecutor customPool = new ThreadPoolExecutor(
    2,                      // 核心线程数
    5,                      // 最大线程数
    60, TimeUnit.SECONDS,   // 空闲线程存活时间
    new LinkedBlockingQueue<>(10),  // 任务队列(容量10)
    new ThreadPoolExecutor.AbortPolicy()  // 拒绝策略(默认抛异常)
);
//模拟固定大小线程池
核心线程数和最大线程数都设置为 5,
这就模拟了固定大小的线程池,线程池始终保持 5 个线程在运行。
import java.util.concurrent.*;

public class FixedThreadPoolExample {
    public static void main(String[] args) {
        // 核心线程数和最大线程数相同,模拟固定大小线程池
        ThreadPoolExecutor fixedThreadPool = new ThreadPoolExecutor(
                5,
                5,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>()
        );
        // 使用线程池执行任务
        fixedThreadPool.execute(() -> System.out.println("Task executed in fixed thread pool."));
        fixedThreadPool.shutdown();
    }
}


//模拟单线程线程池
单线程线程池意味着线程池中始终只有一个线程在执行任务,任务会按照提交的顺序依次执行。
    可以通过设置 ThreadPoolExecutor 的核心线程数和最大线程数都为 1 来模拟单线程线程池。
import java.util.concurrent.*;

public class SingleThreadExecutorSimulation {
    public static void main(String[] args) {
        // 核心线程数和最大线程数都设置为 1,模拟单线程线程池
        ThreadPoolExecutor singleThreadExecutor = new ThreadPoolExecutor(
                1,
                1,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>()
        );

        // 提交多个任务
        for (int i = 0; i < 5; i++) {
            final int taskId = i;
            singleThreadExecutor.execute(() -> {
                System.out.println("Task " + taskId + " is being executed by " + Thread.currentThread().getName());
                try {
                    // 模拟任务执行耗时
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }

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

//模拟缓存线程池
核心线程数为 0,最大线程数是 Integer.MAX_VALUE,使用 SynchronousQueue 作为任务队列,
这模拟了缓存线程池,线程池会根据任务的数量动态创建和销毁线程。
import java.util.concurrent.*;

public class CachedThreadPoolExample {
    public static void main(String[] args) {
        // 核心线程数为 0,最大线程数为 Integer.MAX_VALUE,模拟缓存线程池
        ThreadPoolExecutor cachedThreadPool = new ThreadPoolExecutor(
                0,
                Integer.MAX_VALUE,
                60L, TimeUnit.SECONDS,
                new SynchronousQueue<>()
        );
        // 使用线程池执行任务
        cachedThreadPool.execute(() -> System.out.println("Task executed in cached thread pool."));
        cachedThreadPool.shutdown();
    }
}

//模拟定时任务线程池
定时任务线程池可以让任务在指定的时间后执行,或者按照固定的时间间隔重复执行。
虽然 ThreadPoolExecutor 本身不直接支持定时任务,
但可以结合 ScheduledFuture 和 Delayed 接口来模拟
import java.util.concurrent.*;

public class ScheduledExecutorSimulation {
    public static void main(String[] args) {
        // 创建一个核心线程数为 2 的定时任务线程池
        ScheduledThreadPoolExecutor scheduledExecutor = new ScheduledThreadPoolExecutor(2);

        // 延迟 2 秒后执行任务
        scheduledExecutor.schedule(() -> {
            System.out.println("Delayed task is being executed by " + Thread.currentThread().getName());
        }, 2, TimeUnit.SECONDS);

        // 延迟 1 秒后开始执行任务,之后每隔 3 秒重复执行
        scheduledExecutor.scheduleAtFixedRate(() -> {
            System.out.println("Repeated task is being executed by " + Thread.currentThread().getName());
        }, 1, 3, TimeUnit.SECONDS);

        // 主线程休眠一段时间,以便观察任务执行情况
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

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


2.3、自定义线程池参数设置推荐

1. 核心线程数(corePoolSize)

基于任务耗时与任务量的计算

核心线程数的设置需要参考任务耗时和每秒任务数。以下是具体的计算方法及示例:
假设一个任务耗时 0.1 秒,系统每秒产生 100 个任务。

  • 理想情况:如果想在 1 秒内处理完这 100 个任务,根据公式 单个任务耗时 * 每秒任务数 / corePoolSize = 处理时间,即 0.1 * 100 / corePoolSize = 1,可计算得出 corePoolSize = 10
  • 非紧急情况:如果只是偶尔某一秒产生了 100 个任务,后续有更多时间处理,允许任务积压,如 2 秒,那么 0.1 * 100 / corePoolSize = 2,可得 corePoolSize = 5

根据 80/20 法则,在实际应用中,不会每秒一直保持 100 个任务的产生量,所以最终核心线程数可以设置为计算所得值的 80%。例如上述理想情况计算出的 corePoolSize = 10,最终可设置为 10 * 0.8 = 8。当然,对于偶尔出现的大量任务,还可以依靠缓存队列和最大线程数来保证任务的执行。

案例一

在 CPU 密集型业务场景中,假设主机的 CPU 核心数为 24,系统稳定时平均流量为 20 QPS(每秒查询率),且每个请求的执行时间为 0.2 秒。此时,可通过计算来确定核心线程数。

由于每个请求执行时间为 0.2 秒,平均流量为 20 QPS,那么每秒所需的计算资源为 20×0.2 = 4 个核心。因此,核心线程数设置为 4 个较为合适,这样既能满足业务需求,又不会造成线程资源的浪费。

案例二

同样是 CPU 密集型业务场景,主机的 CPU 核心数依然是 24,但系统稳定时平均流量达到了 200 QPS,每个请求执行时间仍为 0.2 秒。

按照之前的计算方法,每秒所需的计算资源为 200×0.2 = 40 个核心。然而,主机的 CPU 核心数仅有 24,这意味着该主机每秒最多只能处理 24÷0.2 = 120 个任务。也就是说,每秒会有 200 - 120 = 80 个任务无法被及时处理。

在这种情况下,如果不采用直接抛弃多余任务的拒绝策略,随着时间的推移,任务对象会不断堆积,极有可能导致 OOM(Out of Memory,内存溢出)错误。此时,单纯增加线程数已无法解决问题,需要考虑通过增加服务集群中的主机数量来分担流量。例如,可以再添加一台 CPU 核心数为 24 的服务器,以提升整体的处理能力。

基于 CPU/IO 密集型的设置

此外,还可以根据系统的 CPU 的使用特性(是 CPU 密集型还是 IO 密集型)来设置核心线程数:

//在 Java 中,通过 Runtime 类可以获取与系统运行时环境相关的信息和进程控制功能。
//其中的 getRuntime() 方法可以返回当前程序正在运行的 Java 虚拟机(JVM)的运行时实例。
//通过这个实例,我们可以调用 availableProcessors() 方法来获取当前系统中可用的处理器数量。

// 通过 Runtime 类获取系统运行时环境相关信息,进而获取可用处理器数量
//核数
int processorAmount = Runtime.getRuntime().availableProcessors();
  • CPU 密集型任务(线程一直在执行指令:核心线程数可设置为 CPU 内核数 + 1。(避免上下文切换导致的资源浪费)
  • IO 密集型任务(需要通过 IO 获取结果(调用远程服务,查询数据库:线程执行一段指令后需要通过 IO 获取结果(如调用远程服务、查询数据库),核心线程数可设置为 CPU 内核数 × 2。(或更精确的公式:CPU内核数 × (1 + 等待时间/计算时间),根据 IO 等待时间调整)
  • 混合密集型:推荐拆分出具体任务,设置两个线程池,分别执行 CPU 密集任务和 IO 密集任务。若无法拆分,可按 IO 密集型设置核心线程数(因 IO 等待时间通常远大于计算时间),并通过监控动态调整参数。

(CPU密集:核心线程数=CPU内核数 + 1)

(IO密集: 核心线程数=CPU内核数 * 2)

int processorAmount = Runtime.getRuntime().availableProcessors();
// CPU密集型
int corePoolSizeCPU = processorAmount + 1;
// IO密集型
int corePoolSizeIO = processorAmount * 2;
2、最大线程数(maximumPoolSize)

最大线程数的设置需要参考核心线程数、缓存队列长度和每秒最大任务数,一般可设置为 (最大任务数 - 任务队列长度) * 单个任务执行时间。((每秒最大任务数 - 任务队列长度) / (1 / 单个任务执行时间))
假设每秒最大任务数为 1000,任务队列长度为 200,单个任务执行时间为 0.1 秒,则最大线程数为 (1000 - 200) * 0.1 = 80

同时,也可以基于核心线程数来设置最大线程数,可设置为核心线程数的 1.5~2 倍,推荐设置为 核心线程数 + 核心线程数 / 2,避免线程数过多导致上下文切换开销,代码如下:

int maxThreadSize = coreThreadSize + (coreThreadSize / 2);
3. 任务队列长度(workQueue)

队列长度应基于允许的任务积压时间核心线程处理能力

  • 公式队列长度 = 允许的积压时间 × 每秒任务数
    示例:若允许 2 秒积压,核心线程每秒处理 100 个任务,则队列长度为 200。
  • 经验值:对于 IO 密集型任务,队列长度可设置为核心线程数的 5~10 倍(如核心线程数 10 → 队列长度 50~100)。
4. 最大空闲时间(keepAliveTime)

最大空闲时间没有固定的参考值,需要参考系统运行环境和硬件压力进行设定。可以根据系统产生任务的时间间隔来合理设置,以确保在任务量较少时,闲置线程能够及时被回收,避免资源浪费。

5. 拒绝策略(handler)

拒绝策略的选择需要参考任务的重要程度:

  • 任务不重要:可以直接丢弃任务,使用 DiscardPolicy (默认策略,直接抛出异常。适用于任务不可丢失的场景)或 DiscardOldestPolicy(由调用线程处理任务。可避免任务丢失,但会阻塞调用方)。
  • 任务重要:可自行采用缓冲机制,例如实现自定义的 RejectedExecutionHandler 接口,将任务持久化到磁盘或消息队列中,待线程池有空闲资源时再进行处理。

2.4、简单的线程池使用示例

示例1
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;

class MyThreadPoolExecutor{
    //2:创建一个顺序表来接收创建的线程
    private List<Thread> threadList = new ArrayList<>();
    //4创建一个容量合适的阻塞队列
    private BlockingQueue<Runnable> queue = new ArrayBlockingQueue(1000);
    //1:通过一个循环,n的值,来控制产生的线程的数量
    public MyThreadPoolExecutor(int n){
        for (int i = 0; i < n; i++) {
            Thread t = new Thread(()->{
                //6:把要做的任务从任务队列中不停地取出来,并且执行
                while(true){
                    try {
                        //带有阻塞的take取出元素
                        Runnable runnable = queue.take();
                        runnable.run();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
            t.start();
            //3:添加线程
            threadList.add(t);
        }
    }
    //5:提交runnable到队列里面去
    public void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
    }

     // 提交任务到任务队列
    public void submit(Runnable runnable) {
        try {
            // 如果线程池已关闭,抛出异常
            if (isShutDown) {
                throw new IllegalStateException("线程池已关闭,无法提交任务");
            }
            // 将任务放入队列,该方法在队列满时会阻塞
            queue.put(runnable);
        } catch (InterruptedException e) {
            // 当线程在等待任务放入队列时被中断,恢复中断状态
            Thread.currentThread().interrupt();
        }
    }
}
public class ThreadDemo {
    //大逻辑其实就是,把创建的任务提交上去,再把任务取出来,在run执行就可以了就是这么简单
    public static void main(String[] args) throws InterruptedException {
        MyThreadPoolExecutor executor = new MyThreadPoolExecutor(4);
        for (int i = 0 ; i < 1000 ; i++){
            //变量捕获
            int n = i;
            executor.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("执行任务:" + n + "  " + "当前线程为:" + Thread.currentThread().getName());
                }
            });
        }
    }
}
示例2
1、 自定义工具类

通过包装自定义线程池,对外提供静态方法,方便在项目中使用。以下自定义线程池配置为:核心线程数 1,最大线程数 10,缓存队列容量为 10,因此该线程池最大可接收 20 个任务(10 个在运行线程处理,10 个在队列等待)。

package com.test.threadpool;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CustomThreadPoolUtil {
    private static final Logger logger = LoggerFactory.getLogger(CustomThreadPoolUtil.class);
    private static ThreadPoolExecutor pool = null;

    static {
        // 初始化线程池
        pool = new ThreadPoolExecutor(
            1,  // 核心线程数
            10, // 最大线程数
            3, TimeUnit.SECONDS, // 空闲线程存活时间及单位
            new ArrayBlockingQueue<>(10), // 任务缓存队列
            new CustomThreadFactory(), // 自定义线程工厂
            new CustomRejectedExecutionHandler() // 自定义拒绝策略
        );
    }

    // 关闭线程池方法
    public static void destory() {
        pool.shutdown();
    }

    // 提交任务方法
    public static void execute(Runnable r) {
        pool.execute(r);
    }

    // 自定义线程工厂类
    private static class CustomThreadFactory implements ThreadFactory {
        private final AtomicInteger count = new AtomicInteger(0);
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r);
            String threadName = CustomThreadPoolUtil.class.getSimpleName() + count.incrementAndGet();
            t.setName(threadName);
            return t;
        }
    }

    // 自定义拒绝策略类
    private static class CustomRejectedExecutionHandler implements RejectedExecutionHandler {
        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            logger.error("任务执行失败 {}, 线程池已满 {}", r.toString(), executor.toString());
        }
    }
}
2、测试类

创建 21 个任务,故意超出上述自定义线程池最大可处理的 20 个任务量,以测试线程池的处理情况。

package com.test.threadpool;

public class TestThreadPool {
    public static void main(String[] args) {
        int num = 21;
        for (int i = 1; i <= num; i++) {
            int j = i;
            CustomThreadPoolUtil.execute(() -> {
                try {
                    Thread.sleep(100); // 模拟业务运行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " 执行了任务" + j);
            });
        }
        CustomThreadPoolUtil.destory();
    }
}
3、测试结果

执行测试代码后,20 个任务被正常执行,最后一个任务由于超出线程池处理能力被拒绝,触发了自定义的拒绝方法,具体日志信息如下:

任务执行失败 com.threadpool.TestThreadPool$1@3af49f1c, 
线程池已满 java.util.concurrent.ThreadPoolExecutor@19469ea2
[Running, pool size = 10, active threads = 10, queued tasks = 10, completed tasks = 0]
CustomThreadPoolUtil2 执行了任务12
CustomThreadPoolUtil1 执行了任务1
CustomThreadPoolUtil7 执行了任务17
CustomThreadPoolUtil3 执行了任务13
CustomThreadPoolUtil4 执行了任务14
CustomThreadPoolUtil5 执行了任务15
CustomThreadPoolUtil9 执行了任务19
CustomThreadPoolUtil8 执行了任务18
CustomThreadPoolUtil6 执行了任务16
CustomThreadPoolUtil10 执行了任务20
CustomThreadPoolUtil1 执行了任务3
CustomThreadPoolUtil2 执行了任务2
CustomThreadPoolUtil9 执行了任务8
CustomThreadPoolUtil3 执行了任务5
CustomThreadPoolUtil7 执行了任务4
CustomThreadPoolUtil8 执行了任务9
CustomThreadPoolUtil5 执行了任务7
CustomThreadPoolUtil10 执行了任务11
CustomThreadPoolUtil6 执行了任务10
CustomThreadPoolUtil4 执行了任务6

2.5、线程提交任务的三种方式

1. execute方法

execute方法是Executor接口中定义的核心方法,用于提交不需要返回结果的任务,该接口定义如下:

public interface Executor {
    void execute(Runnable command);
}

此方法接收一个实现了 Runnable 接口的任务对象作为参数。由于 Runnable 接口的 run 方法没有返回值,所以使用 execute 方法提交的任务在执行完成后,无法直接获取执行结果。若任务在执行过程中抛出未捕获的异常,线程池会捕获并记录该异常,默认会由线程池的 UncaughtExceptionHandler 进行处理,但无法通过 execute 方法获知具体异常情况。

使用示例
executorService.execute(() -> {
    // 具体业务逻辑
});
主要特点
  1. 无返回值execute 方法没有返回值,无法获取任务的执行结果。
  2. 任务类型限制:只能接受 Runnable 类型的任务。
适用场景

execute 方法适用于执行一些后台辅助任务,如日志记录、数据清理等不需要返回结果的场景。这些任务重点在于执行,而不需要获取执行结果或对任务进行额外的控制。

与 submit 方法对比

总的来说,submit 方法更加灵活,适用于更多场景。它可以提交 CallableRunnable 类型的任务,能返回 Future 对象,可通过该对象获取任务执行结果、取消任务等。而 execute 方法更加简单,适用于只关心任务执行而不需要获取结果的场景。在实际应用中,需根据具体需求选择合适的方法。如果需要获取任务的执行结果、取消任务等,建议使用 submit 方法;若只是执行任务而不关心返回值,则可以使用 execute 方法。

2. submit方法(灵活提交任务并获取结果)

submit方法是ExecutorService接口提供的方法,用于提交任务并返回 Future 对象以控制任务执行流程,共有三种重载形式,支持 CallableRunnable 任务类型,共有以下三个重载方法:

<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
1. 提交 Callable 任务(有返回值)
<T> Future<T> submit(Callable<T> task);
  • 任务类型:接收实现 Callable<T> 接口的任务,其 call() 方法可抛出异常并返回类型为 T 的结果。
  • 返回值Future<T> 对象,通过 future.get() 可阻塞获取任务结果;若任务执行中抛出异常,会封装为 ExecutionException 抛出,需显式捕获处理。
  • 适用场景:需要获取任务执行结果(如计算结果、远程调用返回值)的场景。

示例

class MyCallable implements Callable<Integer> {  
    @Override  
    public Integer call() throws Exception {  
        return 42; // 任务执行结果  
    }  
}  
Future<Integer> future = executorService.submit(new MyCallable());  
try {  
    Integer result = future.get(); // 阻塞等待结果  
} catch (InterruptedException | ExecutionException e) {  
    // 处理中断或任务异常  
}
2. 提交 Runnable 任务并指定自定义结果
<T> Future<T> submit(Runnable task, T result);
  • 任务类型:接收无返回值的 Runnable 任务(run() 方法无返回值)。
  • 特殊功能:可通过参数 T result 指定一个自定义结果,任务执行完成后,future.get() 将直接返回该 result 对象(非任务实际执行结果,仅作为标记)。
  • 适用场景:需要为 Runnable 任务关联一个默认结果(如 “任务完成” 标识),便于统一通过 Future 接口处理。

示例

Future<Void> future = executorService.submit(new MyRunnable(), null);  
// 任务完成后,future.get() 返回 null(自定义结果)
3. 提交 Runnable 任务(无结果,仅状态控制)
Future<?> submit(Runnable task);
  • 任务类型:接收 Runnable 任务。
  • 返回值Future<?> 对象,其 get() 方法返回 null(因 Runnable 无实际结果),但可通过 isDone()isCancelled()cancel() 方法监控任务状态或尝试取消任务。
  • 适用场景:仅需异步执行任务,无需结果,重点关注任务是否完成或需中途取消。
核心特点总结
  1. Future 对象能力
    • 所有 submit 方法均返回 Future,用于获取结果(get())、检查状态(isDone())或取消任务(cancel(boolean))。
    • Callable 任务,get() 返回实际计算结果;对 Runnable 任务,get() 返回 null 或自定义 result
  1. 异常处理
    • Callablecall() 方法若抛出异常,会被包装为 ExecutionException,需在调用 get() 时处理。
    • Runnablerun() 方法若抛出未检查异常,会在任务异步执行时导致线程终止,但不会直接中断主线程,需通过 Future 或日志捕获。
  1. 同步阻塞特性
    • future.get() 是阻塞方法,会等待任务完成;可通过 future.get(long timeout, TimeUnit unit) 设置超时时间,避免永久阻塞。
与 execute 方法的对比
  • execute(Runnable task):仅用于提交无返回值的 Runnable 任务,无 Future 对象,无法获取结果或监控状态。
  • submit:支持 CallableRunnable,返回 Future,提供更灵活的任务控制能力,适用于需要结果或状态管理的场景。

通过合理选择 submit 的重载方法,开发者可在异步任务中高效实现结果获取、状态监控及异常处理,充分发挥线程池的灵活性与可控性。

3. schedule方法

schedule方法由ScheduledExecutorService接口提供,主要用于提交需要延迟执行或周期性执行的任务。其常见方法包括:

ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);
<T> ScheduledFuture<T> schedule(Callable<T> callable, long delay, TimeUnit unit);

这两个方法都会在指定的延迟时间delay(由unit指定时间单位)后执行任务,并返回一个ScheduledFuture对象。通过该对象不仅可以获取任务执行结果(如果是Callable任务)、取消任务,还能查看任务的执行状态。此外,ScheduledExecutorService还提供了scheduleAtFixedRatescheduleWithFixedDelay方法,用于实现按固定频率或固定延迟的周期性任务调度。

4. 三种提交方式的区别
  1. 任务类型支持execute方法仅支持提交Runnable类型任务;submit方法既支持Runnable,也支持Callable类型任务;schedule方法同样支持这两种类型,但侧重于延迟或周期性执行。
  2. 结果获取能力execute方法提交的任务无返回值,无法直接获取执行结果;submit方法提交Callable任务时可以获取结果,提交Runnable任务时可通过特定方式间接关联结果;schedule方法执行Callable任务时也可获取结果,且能结合延迟或周期特性使用。
  3. 异常处理execute方法提交的任务若抛出异常,由线程池默认的异常处理器处理;submit方法通过Future对象的get方法获取任务结果时,可以捕获ExecutionException来处理任务执行过程中的异常,更便于显式处理异常情况;schedule方法的异常处理机制与submit类似,通过返回的ScheduledFuture对象处理异常。
  4. 功能特性executesubmit主要用于立即执行任务;schedule方法则专注于延迟执行或周期性执行任务,适用于定时任务、周期性任务等场景。

3、阻塞队列

在Java线程池中,阻塞队列(BlockingQueue) 是任务调度和资源管理的核心组件,直接影响线程池的任务处理策略和性能

队列类型

数据结构

容量限制

公平性

适用场景

ArrayBlockingQueue

数组

有界(需指定)

可选

固定大小队列,严格资源控制

LinkedBlockingQueue

链表

可选(默认无界)

吞吐量优先,任务量波动较大

SynchronousQueue

无存储(直接传递)

容量为 0

可选

直接传递任务,避免队列堆积

PriorityBlockingQueue

堆(优先级队列)

无界

按优先级执行任务

DelayQueue

优先级堆(延迟)

无界

定时任务、延迟执行

LinkedTransferQueue

链表

无界

可选

高并发场景下的高效传输队列

3.1、ArrayBlockingQueue

特点:基于数组的有界队列,初始化时必须指定容量,支持公平锁(减少线程饥饿)。

  • 底层结构:基于数组实现的有界阻塞队列,在初始化时必须显式指定队列容量,一旦创建,容量不可动态调整。
  • 线程公平性:支持公平锁机制,通过公平策略可以减少线程饥饿现象,即按照线程等待的先后顺序分配资源,确保先等待的线程优先获取执行机会 。

线程池行为:当核心线程满且队列未满时,任务入队等待。队列满时,若线程数未达最大线程数,创建新线程;否则触发拒绝策略。

  • 当线程池中的核心线程均处于忙碌状态,且队列尚未达到最大容量时,新提交的任务将被放入队列中等待执行。
  • 若队列已满,此时若线程数量未达到线程池设定的最大线程数,则会创建新的线程来处理任务;若线程数已达最大线程数,新任务将触发线程池的拒绝策略。

适用场景:

适用于对资源使用需进行严格控制的场景。例如在高并发但任务处理时间较短的系统中,通过设定固定容量的ArrayBlockingQueue,既能有效限制任务积压量,防止内存过度占用,又能利用公平锁机制保障线程执行的有序性,提升系统整体稳定性。

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ArrayBlockingQueueExample {
    public static void main(String[] args) {
        // 创建容量为10的ArrayBlockingQueue
        BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(10);
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                2,      // 核心线程数
                5,      // 最大线程数
                60,     // 空闲线程存活时间
                TimeUnit.SECONDS,
                queue
        );
        // 可继续添加提交任务等逻辑
    }
}

3.2、LinkedBlockingQueue

特点:基于链表的队列,默认无界(Integer.MAX_VALUE),也可指定容量(有界)。

  • 底层结构:基于链表实现的阻塞队列,其容量具有灵活性。默认情况下,队列容量为Integer.MAX_VALUE,表现为无界队列;也可在创建时指定容量,使其成为有界队列 。
  • 结构优势:链表结构使得在插入和删除元素时无需像数组那样进行大量的数据移动,在任务频繁入队和出队操作时,能展现出更高效的性能表现。

线程池行为:

  • 无界队列:当使用默认无界的LinkedBlockingQueue时,新提交的任务总能入队,此时线程池设定的最大线程数(maximumPoolSize)参数失去作用,线程池中的线程数量仅维持在核心线程数,只有核心线程参与任务执行,多余任务全部积压在队列中。
  • 有界队列:若指定队列容量使其成为有界队列,其行为与ArrayBlockingQueue类似。当核心线程满且队列未满时,任务入队;队列满且线程数未达最大线程数时创建新线程;队列满且线程数已达最大时触发拒绝策略,但链表结构在数据操作上更为灵活。

适用场景:

适合应用于任务量波动较大且允许一定程度任务积压的场景。例如在异步日志处理系统中,日志记录任务产生的频率和数量可能存在较大变化,使用LinkedBlockingQueue可以在任务高峰期暂时缓存日志任务,保证系统不会因瞬间大量任务而崩溃。不过在使用默认无界模式时需谨慎,因为无限增长的队列可能导致内存溢出(OOM)问题。

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class LinkedBlockingQueueExample {
    public static void main(String[] args) {
        // 默认无界队列(慎用,可能导致OOM)
        BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();

        // 有界队列(容量100)
        BlockingQueue<Runnable> boundedQueue = new LinkedBlockingQueue<>(100);

        ThreadPoolExecutor executorUnbounded = new ThreadPoolExecutor(
                2, 5, 60, TimeUnit.SECONDS, queue
        );

        ThreadPoolExecutor executorBounded = new ThreadPoolExecutor(
                2, 5, 60, TimeUnit.SECONDS, boundedQueue
        );
        // 可继续添加提交任务等逻辑
    }
}

3.3、SynchronousQueue

特点:不存储元素,每个插入操作必须等待另一个线程的移除操作(一对一传输)。

零存储特性SynchronousQueue 是一种特殊的阻塞队列,它本身不具备存储元素的能力。每一次插入操作都必须等待另一个线程执行移除操作,这体现了一种一对一的数据传输机制,保证了元素在生产者和消费者之间的直接传递,中间没有缓冲环节

线程池行为:任务直接交给可用线程,若无空闲线程且未达最大线程数,则创建新线程。若线程数已达最大值,立即触发拒绝策略。

  • 任务即时分配:当有新任务提交到使用 SynchronousQueue 的线程池时,线程池会尝试直接将任务分配给可用的空闲线程。如果当前没有空闲线程,并且线程池中的线程数量尚未达到最大线程数,线程池会立即创建一个新线程来处理该任务。
  • 拒绝策略触发:一旦线程池中的线程数量达到了预先设定的最大值,此时若再有新任务提交,线程池会立即触发拒绝策略,因为队列无法存储任务,也没有多余的线程来处理。

适用场景:

适用于对响应速度有较高要求的场景,特别是那些任务处理速度快且不希望任务在队列中积压的场景。例如实时请求处理系统,此类系统需要快速响应用户的请求,SynchronousQueue 能够确保任务立即得到处理,避免了任务排队等待带来的延迟。

// 使用SynchronousQueue(通常搭配最大线程数为无界或较大值)
BlockingQueue<Runnable> queue = new SynchronousQueue<>();
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, queue
);

3.4、PriorityBlockingQueue

特点:无界优先级队列,任务按自然顺序或自定义Comparator排序。

  • 无界且有序PriorityBlockingQueue 是一种无界的阻塞队列,它能够根据任务的优先级对任务进行排序。任务可以按照自然顺序进行排序(如果任务类实现了 Comparable 接口),也可以通过自定义的 Comparator 来指定排序规则。

线程池行为:任务按优先级执行,高优先级任务先被处理。

在使用 PriorityBlockingQueue 的线程池中,任务会按照其优先级顺序执行。高优先级的任务会优先被线程池中的线程获取并处理,从而确保重要的任务能够得到及时响应。

适用场景:需要任务调度优先级的场景,如VIP用户请求优先处理。

适用于需要对任务调度进行优先级管理的场景。例如在一些服务系统中,对于 VIP 用户的请求可以设置较高的优先级,使用 PriorityBlockingQueue 能够保证 VIP 用户的请求优先得到处理,提升用户体验。

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.Comparator;

// 定义一个任务类,实现 getPriority 方法用于获取优先级
class Task implements Runnable {
    private int priority;

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

    public int getPriority() {
        return priority;
    }

    @Override
    public void run() {
        // 任务执行逻辑
    }
}

public class PriorityBlockingQueueExample {
    public static void main(String[] args) {
        // 创建优先级队列(需实现 Comparable 或提供 Comparator)
        BlockingQueue<Runnable> queue = new PriorityBlockingQueue<>(10, Comparator.comparing(Task::getPriority));
        // 可以继续添加向队列添加任务或使用线程池处理队列任务的代码
    }
}

3.5、DelayQueue

  • 特点DelayQueue 专门用于存储实现了 Delayed 接口的元素,只有当元素的延迟时间到期后,才能够从队列中取出该元素。
  • 线程池行为:在实际应用中,DelayQueue 常被用于线程池执行定时任务或者延迟任务,借助元素的延迟特性实现任务的定时调度。
  • 适用场景:适用于各类定时任务调度场景,例如缓存过期清理、延迟消息处理等。
// 示例:定义延迟任务类
public class DelayedTask implements Delayed {
    private final long executeTime;

    public DelayedTask(long delay) {
        // 计算任务的执行时间,即当前时间加上延迟时间
        this.executeTime = System.currentTimeMillis() + delay;
    }

    @Override
    public long getDelay(TimeUnit unit) {
        // 计算任务距离执行还剩余的时间
        return unit.convert(executeTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
    }

    @Override
    public int compareTo(Delayed o) {
        // 比较两个任务的执行时间,用于队列排序
        return Long.compare(this.executeTime, ((DelayedTask) o).executeTime);
    }
}

// 使用 DelayQueue 的示例
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;

public class DelayQueueExample {
    public static void main(String[] args) {
        BlockingQueue<DelayedTask> queue = new DelayQueue<>();
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
        // 可继续添加使用 queue 和 executor 的代码逻辑
    }
}

3.6、队列选择策略与最佳实践

任务特性分析

  • 短任务、高吞吐场景:推荐使用 LinkedBlockingQueue(有界)或者 ArrayBlockingQueue。这两种队列能够有效处理大量短时间任务,确保高吞吐量。
  • 实时性要求高的场景SynchronousQueue 是比较合适的选择,但需要合理设置线程池的最大线程数。因为该队列本身不存储任务,任务提交后需要立即有线程来处理。
  • 优先级任务场景PriorityBlockingQueue 能根据任务的优先级对任务进行排序,确保高优先级任务优先执行。
  • 延迟 / 定时任务场景DelayQueue 凭借其元素延迟特性,能够精准实现任务的定时调度。

资源限制考虑

  • 内存敏感场景:建议使用有界队列,如 ArrayBlockingQueue,避免因队列无限增长导致内存溢出(OOM)。
  • CPU 密集型任务场景:选择较小的队列容量,减少线程上下文切换带来的开销,提高 CPU 利用率。
  • IO 密集型任务场景:可以设置较大的队列容量,以应对可能出现的任务堆积情况。

3.7、拒绝策略配合

在使用有界队列时,必须设置合理的拒绝策略。例如,CallerRunsPolicy 可以将无法处理的任务交还给调用者线程执行,起到降级处理的作用,保障系统的稳定性。

4、拒绝策略

当线程池的任务队列已满,并且所有工作线程都处于忙碌状态时,新提交的任务会触发拒绝策略(RejectedExecutionHandler)。拒绝策略对于处理这些无法立即处理的任务至关重要,它是保障系统稳定性和任务可靠性的关键机制。

4.1、AbortPolicy(默认策略)

行为:直接抛出 RejectedExecutionException 异常,阻止任务提交。

AbortPolicyThreadPoolExecutor 的默认拒绝策略。当线程池的任务队列已满,且当前线程数量已达到最大线程数时,若再有新任务提交,该策略会直接抛出 RejectedExecutionException 异常,从而立即中断任务提交操作。这种方式通过异常机制清晰地传递任务提交失败的信息,强制任务提交方处理任务被拒的情况。

适用场景:适用于对任务处理要求严格,不允许任务丢失的场景。同时,需要显式处理异常,让任务提交方明确知道任务被拒绝的情况。

  • 任务完整性要求极高的场景:在对任务处理要求极为严格,不允许任何任务丢失的业务场景中,AbortPolicy 是理想选择。例如金融交易系统中的资金转账任务、电商平台的订单生成任务等,这类任务一旦丢失,可能导致严重的业务错误或经济损失,必须通过异常机制及时反馈任务提交失败,确保业务流程的正确性和完整性。
  • 需要明确错误反馈的场景:当系统需要显式告知任务提交方任务被拒绝的情况时,AbortPolicy 能够满足需求。通过捕获 RejectedExecutionException 异常,提交方可以根据具体业务逻辑进行针对性处理,如提示用户稍后重试、记录错误日志、触发补偿机制等,让系统具备清晰的错误处理链路。

4.2、CallerRunsPolicy

行为:将任务回退给提交任务的线程(调用者线程)直接执行。

CallerRunsPolicy 是一种线程池拒绝策略,当线程池任务队列已满且达到最大线程数时,该策略会将新提交的任务回退给提交任务的线程(即调用者线程),由调用者线程直接执行该任务。这种机制避免了任务因被拒绝而丢失,转而利用调用者线程的资源来处理任务 。
适用场景:生产速度远大于消费速度时,通过调用者线程执行任务,自然降低提交速率。作为临时降级策略,避免任务丢失。

  • 流量调控场景:在生产速度远大于消费速度的场景下,新任务不断积压导致线程池饱和。此时采用 CallerRunsPolicy,调用者线程执行任务会占用自身资源,从而降低其提交新任务的频率,自然地对任务提交速率进行调节,实现生产与消费的动态平衡。
  • 临时降级策略:当系统面临突发流量高峰或资源紧张情况时,可将 CallerRunsPolicy 作为临时的降级策略。它能确保任务不被直接丢弃,在一定程度上维持系统的基本功能运行,避免关键任务丢失影响业务流程 。

风险:若提交任务的线程是主线程或关键线程,可能导致主线程阻塞。

若提交任务的线程是主线程或其他关键线程,当执行回退任务时,可能导致主线程或关键线程被阻塞。这会影响主线程后续流程的执行,甚至造成用户界面卡顿、响应延迟等问题,因此在使用时需要谨慎评估调用者线程的重要性。

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class CallerRunsPolicyExample {
    public static void heavyTask() {
        try {
            // 模拟耗时任务
            Thread.sleep(2000);
            System.out.println("任务执行完成");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                2,          // 核心线程数
                5,          // 最大线程数
                60,         // 空闲线程存活时间
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(10),  // 容量为10的有界队列
                new ThreadPoolExecutor.CallerRunsPolicy()  // 回退给调用者线程执行
        );
        // 主线程提交任务,当队列满时可能由主线程执行任务
        executor.execute(() -> heavyTask());
        executor.shutdown();
    }
}

4.3、DiscardPolicy

行为:静默丢弃被拒绝的任务,无任何通知。

DiscardPolicy 是一种简单直接的拒绝策略,当线程池无法处理新提交的任务(任务队列已满且达到最大线程数)时,该策略会直接静默丢弃被拒绝的任务,既不抛出异常,也不进行任何形式的通知 。
适用场景:允许任务丢失的非关键场景(如日志记录、心跳检测)。任务具有时效性,过期后无意义。

  • 非关键任务场景:适用于允许任务丢失的非关键业务场景,例如某些日志记录任务。即使部分日志记录任务因线程池拒绝而丢失,也不会对系统的核心功能和业务逻辑产生实质性影响;又如心跳检测任务,偶尔丢失一两次心跳检测请求,不影响整体的健康状态判断 。
  • 时效性任务场景:对于具有时效性的任务,如果任务在等待处理过程中已过期,失去了处理的意义,此时采用 DiscardPolicy 丢弃过期任务是合理的选择,可避免无效的资源消耗。

风险:任务丢失可能导致数据不一致,需结合监控使用。

任务的静默丢弃可能导致数据不一致问题,例如在一些数据采集或处理流程中,丢失任务可能造成数据缺失。因此,在使用该策略时,需要结合完善的监控机制,及时发现任务丢失情况,并通过其他补偿手段来保证业务数据的完整性。

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class DiscardPolicyExample {
    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                2,          // 核心线程数
                5,          // 最大线程数
                60,         // 空闲线程存活时间
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(10),  // 容量为10的有界队列
                new ThreadPoolExecutor.DiscardPolicy()  // 直接丢弃被拒绝的任务
        );
        // 提交任务,若被拒绝将被直接丢弃
        executor.execute(() -> System.out.println("尝试执行任务"));
        executor.shutdown();
    }
}

4.4、DiscardOldestPolicy

行为:丢弃队列中最旧的任务(即队列头部的任务),然后重试提交当前任务。

DiscardOldestPolicy 策略在任务被拒绝时(线程池任务队列已满且达到最大线程数),会先丢弃队列中最旧的任务(即位于队列头部、等待时间最长的任务),然后重新尝试提交当前任务。这一过程旨在为新任务腾出队列空间,优先处理相对较新的任务。
适用场景:新任务比旧任务优先级更高(如实时数据覆盖历史数据)。队列中旧任务可能已过时或无意义。

  • 任务优先级动态变化场景:当新任务比旧任务优先级更高时,例如在实时数据处理场景中,新产生的实时数据需要优先处理,覆盖掉旧的历史数据,此时采用 DiscardOldestPolicy 可确保新数据得到及时处理 。
  • 任务时效性明显场景:队列中的旧任务可能因时间推移而过时、失去处理意义,例如某些限时优惠活动的订单处理,过期的订单无需再执行,优先处理新订单能更好地满足业务需求。

风险:可能丢弃重要任务,需谨慎评估业务逻辑。

由于直接丢弃队列头部的旧任务,可能会误删一些重要任务。如果业务逻辑中旧任务仍然具有关键作用,如某些按顺序处理的流程任务,丢弃旧任务可能导致业务流程出错或数据不完整,因此在使用该策略前,需对业务逻辑进行全面、谨慎的评估。

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class DiscardOldestPolicyExample {
    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                2,          // 核心线程数
                5,          // 最大线程数
                60,         // 空闲线程存活时间
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(10),  // 容量为10的有界队列
                new ThreadPoolExecutor.DiscardOldestPolicy()  // 丢弃最旧任务并重试提交当前任务
        );
        // 提交任务,若队列满将丢弃最旧任务并尝试提交当前任务
        executor.execute(() -> System.out.println("尝试执行任务"));
        executor.shutdown();
    }
}

4.5、自定义拒绝策略

在 Java 线程池的使用中,当内置的拒绝策略无法满足业务需求时,可以通过实现RejectedExecutionHandler接口来自定义拒绝策略,从而实现任务记录日志、持久化存储、异步重试等多样化处理逻辑。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;

public class RetryWithLogPolicy implements RejectedExecutionHandler {
    private static final Logger logger = LoggerFactory.getLogger(RetryWithLogPolicy.class);

    @Override
    public void rejectedExecution(Runnable task, ThreadPoolExecutor executor) {
        // 记录任务被拒绝的详细信息
        logger.warn("任务被拒绝: {}", task);
        
        // 异步重试机制,尝试重新提交任务
        if (!executor.isShutdown()) {
            try {
                // 简单重试提交,注意此方式可能引发递归拒绝
                executor.submit(task); 
            } catch (Exception e) {
                // 若重试失败,可添加更复杂的处理逻辑,如记录到延迟队列或外部存储
                logger.error("任务重试失败", e);
            }
        }
    }
}

// 使用自定义策略
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class CustomRejectionPolicyExample {
    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
            2, 5, 60, TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(10),
            new RetryWithLogPolicy()
        );
        
        // 提交任务,若触发拒绝策略,将执行自定义逻辑
        executor.execute(() -> System.out.println("执行任务"));
        executor.shutdown();
    }
}

4.6、拒绝策略的选择与最佳实践

策略

适用场景

注意事项

AbortPolicy

严格不允许任务丢失,对任务完整性要求极高的场景(如金融交易、订单处理)。

必须在调用端捕获RejectedExecutionException异常,避免因未处理异常导致系统崩溃。

CallerRunsPolicy

生产-消费速度不平衡,需动态调节任务提交速率的场景(如Web服务器请求过载)。

避免在主线程或关键线程中使用,防止因任务执行阻塞主线程,引发级联性能问题。

DiscardPolicy

非核心业务场景,允许任务丢失的场景(如监控数据上报、非关键日志记录)。

需监控丢弃任务量,避免数据丢失影响业务,若任务丢失影响业务统计或分析,需调整策略。。

DiscardOldestPolicy

新任务时效性或优先级显著高于旧任务的场景(如实时消息推送、高频数据更新)。

确保旧任务可丢弃,避免重要任务丢失。

自定义策略

需与外部系统交互(如写入消息队列)、任务持久化存储或复杂重试逻辑的场景。

避免无限递归提交,需设置重试次数限制或超时机制,防止资源耗尽。

5、线程工厂(定制化线程创建的核心工具)

线程工厂(ThreadFactory) 是用于创建新线程的核心接口,它允许开发者自定义线程的创建逻辑(如自定义线程名称、设置线程优先级、配置守护线程属性等)。通过合理使用线程工厂,可以提升线程池的可维护性、调试效率和系统稳定性。

5.1、为什么需要线程工厂?

在 Java 线程池的默认实现中,Executors.defaultThreadFactory() 用于创建线程,但这种方式存在以下局限性:

  1. 线程命名不友好:默认名称为 pool-1-thread-1,在系统出现问题时,难以快速通过线程名称定位问题根源,增加了调试难度。
  2. 无法统一配置:默认线程工厂无法批量设置线程的优先级、守护线程属性等配置。
  3. 缺乏异常处理:若线程内部未捕获异常,可能导致程序在无任何提示的情况下静默崩溃,严重影响系统的稳定性和可靠性。

5.2、ThreadFactory 接口定义

ThreadFactory 是一个函数式接口,只包含一个方法

该方法接收一个 Runnable 任务作为参数,并返回一个新创建的线程实例,为开发者自定义线程创建逻辑提供了入口。

public interface ThreadFactory {
    // 根据传入的Runnable任务创建新线程
    Thread newThread(Runnable r); 
}

5.3、自定义线程工厂示例:

基础实现(自定义线程命名与优先级)

import java.util.concurrent.atomic.AtomicInteger;

public class CustomThreadFactory implements ThreadFactory {
    // 线程名称前缀,便于标识线程所属业务模块
    private final String namePrefix;
    // 线程优先级,可根据业务需求设置
    private final int priority;
    // 原子计数器,用于生成唯一的线程编号
    private final AtomicInteger counter = new AtomicInteger(1);

    public CustomThreadFactory(String namePrefix, int priority) {
        this.namePrefix = namePrefix;
        this.priority = priority;
    }

    @Override
    public Thread newThread(Runnable r) {
        Thread thread = new Thread(r);
        // 为线程设置具有业务含义的名称,如 "service-1"
        thread.setName(namePrefix + counter.getAndIncrement());
        // 设置线程优先级,例如使用Thread.NORM_PRIORITY = 5表示普通优先级
        thread.setPriority(priority);
        // 设置线程为非守护线程(默认情况下,线程即为非守护线程)
        thread.setDaemon(false);
        return thread;
    }
}

5.4、增强实现(全局异常捕获)

通过自定义线程工厂的实现,可以有效解决默认线程工厂存在的问题,使线程的创建更加符合业务需求,同时提升系统的可维护性和稳定性。

public class EnhancedThreadFactory implements ThreadFactory {
   
    private final String namePrefix;
    private final Thread.UncaughtExceptionHandler exceptionHandler;

    public EnhancedThreadFactory(String namePrefix, Thread.UncaughtExceptionHandler handler) {
   
        this.namePrefix = namePrefix;
        this.exceptionHandler = handler;
    }

    @Override
    public Thread newThread(Runnable r) {
   
        Thread thread = new Thread(r);
        thread.setName(namePrefix + "-" + thread.getId());
        // 设置未捕获异常处理器
        thread.setUncaughtExceptionHandler(exceptionHandler);
        return thread;
    }
}

6、提交任务

在 Java 线程池中,根据任务是否需要返回值、是否需要定时执行等需求,可采用不同的方法提交任务,具体如下:

6.1、提交无需返回值的任务(Runnable)

当任务仅需执行特定操作,无需返回执行结果时,可使用 execute() 方法提交实现了 Runnable 接口的任务。该方法直接将任务提交给线程池执行,不会返回任何结果。

execute() 方法适用于执行简单的异步操作,如日志记录、数据清理等。若执行过程中抛出未捕获的异常,会由线程池的 UncaughtExceptionHandler 进行处理 。

// 使用execute()提交任务
customPool.execute(() -> {
    System.out.println("Runnable任务执行线程:" + Thread.currentThread().getName());
});

6.2、提交需要返回值的任务(Callable)

若任务执行后需要获取返回结果,可使用 submit() 方法提交实现了 Callable 接口的任务。该方法会返回一个 Future 对象,通过该对象可获取任务的执行结果或取消任务。

Future.get() 方法为阻塞式调用,调用线程会一直等待直至任务完成并返回结果;若任务执行过程中抛出异常,会封装在 ExecutionException 中。设置超时参数可防止线程因任务长时间未完成而无限阻塞。

// 使用submit()提交任务并获取Future对象
Future<Integer> future = customPool.submit(() -> {
    // 模拟任务执行耗时
    Thread.sleep(1000);
    return 42;
});

// 通过Future对象获取任务结果(该操作会阻塞当前线程,直至任务完成)
try {
    // 可设置超时时间,避免无限等待
    int result = future.get(2, TimeUnit.SECONDS);
    System.out.println("任务结果:" + result);
} catch (InterruptedException e) {
    // 捕获线程被中断异常
    Thread.currentThread().interrupt();
    System.err.println("任务执行被中断");
} catch (ExecutionException e) {
    // 捕获任务执行过程中抛出的异常
    System.err.println("任务执行异常:" + e.getCause());
}

6.3、提交定时/周期任务

对于需要延迟执行或周期性执行的任务,可使用 ScheduledExecutorService 接口。该接口继承自 ExecutorService,提供了丰富的定时任务调度功能。

  • schedule() 方法用于在指定延迟时间后执行单次任务;
  • scheduleAtFixedRate() 按照固定频率执行任务,无论上一次任务是否执行完成,都会按间隔时间启动新任务,可能导致任务并发执行;
  • scheduleWithFixedDelay() 则是在上一次任务执行结束后,等待固定间隔时间再启动下一次任务,确保任务不会重叠执行 。任务执行完毕后,需调用 shutdown()shutdownNow() 关闭线程池,避免资源浪费。
// 创建一个核心线程数为2的定时任务线程池
ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(2);

// 延迟5秒执行一次任务
scheduledPool.schedule(() -> {
    System.out.println("延迟任务执行");
}, 5, TimeUnit.SECONDS);

// 以固定频率执行任务(每3秒执行一次,无视任务实际耗时,可能导致任务堆积)
scheduledPool.scheduleAtFixedRate(() -> {
    System.out.println("固定频率任务");
}, 1, 3, TimeUnit.SECONDS);

// 以固定间隔执行任务(每次任务执行完毕后,间隔2秒再执行下一次)
scheduledPool.scheduleWithFixedDelay(() -> {
    System.out.println("固定间隔任务");
}, 1, 2, TimeUnit.SECONDS);

7、关闭线程池

7.1、线程池的 5 种状态

线程池在其生命周期内会处于不同的状态,这些状态通过一些常量来表示

private static final int RUNNING    = -1 << COUNT_BITS;
private static final int SHUTDOWN   =  0 << COUNT_BITS;
private static final int STOP       =  1 << COUNT_BITS;
private static final int TIDYING    =  2 << COUNT_BITS;
private static final int TERMINATED =  3 << COUNT_BITS;
  • RUNNING:线程池创建之后的初始状态,处于该状态时,线程池可以正常接收并执行新任务。
  • SHUTDOWN:此状态下线程池不再接受新任务,不过会将工作队列中的剩余任务执行完毕。
  • STOP:线程池进入该状态后,既不接受新任务,也不会处理工作队列中的剩余任务,并且会中断所有正在工作的线程。
  • TIDYING:当所有任务都已终止或者处理完成时,线程池会进入此状态,随后将会执行 terminated() 钩子方法。
  • TERMINATED:在执行完 terminated() 钩子方法之后,线程池处于该状态。

线程状态转换图:

RUNNING → SHUTDOWN:调用 shutdown()

RUNNING → STOP:调用 shutdownNow()

SHUTDOWN → TIDYING:任务队列和线程池均为空

STOP → TIDYING:线程池为空

TIDYING → TERMINATED:执行 terminated() 方法后

7.2、关闭线程池的方法

在 Java 多线程编程中,合理关闭线程池是保障系统资源有效释放、避免任务残留的关键环节。线程池的关闭方式分为平缓关闭强制关闭两种,开发者可根据实际业务需求选择合适的关闭策略。

1、平缓关闭(shutdown() 方法)

等待所有已提交任务完成。调用shutdown()后,线程池的状态会转为 SHUTDOWN,不再接收新的任务,但会等待当前工作队列中的剩余任务全部执行完成之后,才会真正关闭线程池。

策略说明:平缓关闭旨在优雅地停止线程池,它首先拒绝接收新任务,然后等待所有已提交的任务(包括正在执行和排队等待的任务)自然执行完毕。这种方式适用于对任务完整性要求较高的场景,可确保任务无丢失且资源有序释放。

// 调用shutdown()方法,线程池不再接收新任务,但会继续处理已提交任务
customPool.shutdown(); 

try {
    // 调用awaitTermination()方法,阻塞当前线程,等待线程池在指定时间内关闭
    // 若60秒内所有任务执行完毕,线程池关闭,该方法返回true;否则返回false
    if (!customPool.awaitTermination(60, TimeUnit.SECONDS)) {
        System.out.println("线程池未在60秒内关闭");
        // 可在此处添加额外的处理逻辑,如再次尝试关闭或记录日志
    }
} catch (InterruptedException e) {
    // 捕获线程中断异常,通常在等待过程中被其他线程中断时触发
    Thread.currentThread().interrupt();
    System.err.println("等待线程池关闭时被中断");
}
2、强制关闭(shutdownNow() 方法)

立即中断所有任务。调用shutdownNow() 后,线程池会立即停止接收新任务,打断正在执行的工作线程,并且清空当前工作队列中的剩余任务,同时返回尚未执行的任务列表

策略说明:强制关闭会立即中断所有正在执行的任务,并清空任务队列,返回所有未执行的任务列表。这种方式适用于需要快速释放资源、终止紧急任务的场景,但可能导致部分任务未完成或数据不一致。

// 调用shutdownNow()方法,线程池立即停止接收新任务,中断所有正在执行的任务
// 并返回任务队列中所有未执行的任务列表
List<Runnable> unfinished = customPool.shutdownNow(); 
if (!unfinished.isEmpty()) {
    System.out.println("以下任务未执行:" + unfinished);
    // 可根据业务需求,对未执行任务进行额外处理,如重新提交、持久化存储等
}
3、awaitTermination() 方法

该方法用于等待线程池完成关闭。在调用 shutdown()shutdownNow() 方法之后,用户程序不会主动等待线程池关闭完成,此时可以使用 awaitTermination() 方法。在设置的时间 timeout 内,如果线程池完成关闭,该方法返回 true,否则返回 false。如上述 shutdown() 方法示例中就使用了该方法来等待线程池关闭。

注意事项

  • 关闭线程池后,若再次提交任务,会触发拒绝策略(通常抛出 RejectedExecutionException 异常)。
  • 实际应用中,建议优先采用平缓关闭;仅在极端情况下(如系统崩溃前强制回收资源)使用强制关闭。
  • 配合日志记录和监控机制,可更好地追踪线程池关闭过程中的任务状态和资源释放情况。

7.3、如何保证重启服务时,线程池的阻塞队列中排队中的任务不会丢失?

使用一个ThreadPoolUtils统一管理我们系统所需要的全部线程池,使用@PreDestroy定义一个springboot服务关闭前自动调用的方法

@Component
public class ThreadPoolUtils {

    public static final ExecutorService CAMPAIGN_EXECUTOR_POOL =
            new ThreadPoolExecutor(
                    8,
                    8,
                    0L,
                    TimeUnit.MILLISECONDS,
                    new LinkedBlockingQueue<Runnable>(),
                    new DefaultThreadFactory("CAMPAIGN_EXECUTOR_POOL"));

    public static final ExecutorService FB_ASYNC_EXECUTOR = new ThreadPoolExecutor(
            2,
            10,
            0L,
            TimeUnit.MILLISECONDS,
            new SynchronousQueue<>(),
            new DefaultThreadFactory("FB_ASYNC_EXECUTOR"),
            new ThreadPoolExecutor.CallerRunsPolicy());

    public static final ExecutorService GG_ASYNC_EXECUTOR = new ThreadPoolExecutor(
            2,
            10,
            0L,
            TimeUnit.MILLISECONDS,
            new SynchronousQueue<>(),
            new DefaultThreadFactory("GG_ASYNC_EXECUTOR"),
            new ThreadPoolExecutor.CallerRunsPolicy());

    public static final ExecutorService MPA_ASYNC_SHARED_AUDIENCE_EXECUTOR = new ThreadPoolExecutor(
            4,
            10,
            0L,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(300),
            new DefaultThreadFactory("MPA_ASYNC_SHARED_AUDIENCE_EXECUTOR"),
            new ThreadPoolExecutor.AbortPolicy());


    @PreDestroy
    public void shutdownExecutor() throws NoSuchMethodException {
        System.out.println("Shutting down the executor service...");
        Field[] fields = this.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (ExecutorService.class.isAssignableFrom(field.getType())) {
                field.setAccessible(true);
                try {
                    ExecutorService executorService = (ExecutorService) field.get(this);
                    executorService.shutdown(); // Shutdown the executor service gracefully
                    try {
                        // 等待直到所有已经提交的任务完成,超时设置为 5 秒
                        if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
                            System.out.println("Forcing shutdown...");
                            executorService.shutdownNow(); // 强制关闭
                        }
                    } catch (InterruptedException e) {
                        System.err.println("Shutdown interrupted");
                        executorService.shutdownNow(); // 发生异常时强制关闭
                        Thread.currentThread().interrupt();
                    }
                    System.out.println("Executor service shutdown complete.");
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

8. 监控当前线程池

在 Java 里,ThreadPoolExecutor 类提供了一系列实用的 API,能帮助我们监控线程池的运行状态。借助这些 API,我们可以获取诸如正在工作的线程数量、任务队列的长度以及已完成的任务数量等重要信息,从而更好地了解线程池的工作情况,进行性能调优和问题排查。

以下是常用的监控方法及其作用:

方法

作用

getActiveCount()

返回正在执行任务的线程数。不过需注意,这是一个近似值,可能无法实时准确反映当前正在执行任务的线程数量。

getPoolSize()

返回线程池中当前的线程总数,这里面包含了空闲线程。

getCorePoolSize()

返回线程池的核心线程数,这是创建线程池时配置的参数。

getMaximumPoolSize()

返回线程池允许的最大线程数,同样是配置参数。

getLargestPoolSize()

返回线程池历史上同时存在的最大线程数,也就是线程池线程数量的峰值。

getCompletedTaskCount()

返回线程池已完成的任务总数,此为近似值。

getTaskCount()

返回线程池已调度执行的任务总数,涵盖了已完成的任务、正在执行的任务以及在任务队列中等待的任务。

getQueue()

返回线程池的任务队列对象(BlockingQueue

),通过这个对象可以进一步获取队列的长度等信息。

示例代码:

import java.util.concurrent.*;

public class ThreadPoolMonitorDemo {
    public static void main(String[] args) {
        // 创建一个线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                2,  // 核心线程数
                5,  // 最大线程数
                60, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(10),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );

        // 向线程池提交一些任务
        for (int i = 0; i < 15; i++) {
            final int taskId = i;
            executor.execute(() -> {
                try {
                    // 模拟任务执行,睡眠 1 秒
                    Thread.sleep(1000);
                    System.out.println("Task " + taskId + " done.");
                } catch (InterruptedException e) {
                    // 处理中断异常
                    Thread.currentThread().interrupt();
                }
            });
        }

        // 启动一个监控线程,定期打印线程池的状态
        new Thread(() -> {
            while (true) {
                try {
                    // 每 0.5 秒进行一次监控
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    // 若线程被中断,退出循环
                    break;
                }
                System.out.println("\n--- 线程池状态监控 ---");
                System.out.println("活动线程数: " + executor.getActiveCount());
                System.out.println("当前线程总数: " + executor.getPoolSize());
                System.out.println("历史最大线程数: " + executor.getLargestPoolSize());
                System.out.println("已完成任务数: " + executor.getCompletedTaskCount());
                System.out.println("队列中待处理任务数: " + executor.getQueue().size());
            }
        }).start();

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

输出示例:

--- 线程池状态监控 ---
活动线程数: 5
当前线程总数: 5
历史最大线程数: 5
已完成任务数: 0
队列中待处理任务数: 10

--- 线程池状态监控 ---
活动线程数: 5
当前线程总数: 5
历史最大线程数: 5
已完成任务数: 5

9. 处理异常

在 Java 线程池的使用过程中,任务执行时可能会抛出异常。为了确保程序的稳定性和可维护性,需要采用不同的策略来处理这些异常。常见的异常处理方式包括任务内部捕获、通过Future获取异常以及设置全局异常处理器,具体如下:

9.1、任务内部捕获异常

在任务代码块内部使用try-catch语句块捕获异常,是最直接的异常处理方式。这种方式适用于对异常处理有明确业务逻辑的场景,能够在任务执行过程中及时处理异常,避免异常扩散影响其他任务或线程。

customPool.execute(() -> {
    try {
        // 业务代码,例如数据库操作、网络请求等
        // 可能会抛出各种异常
    } catch (Exception e) {
        // 捕获异常并进行处理
        System.err.println("任务异常: " + e);
        // 可根据业务需求添加更多处理逻辑,如记录日志、进行补偿操作等
    }
});

注意事项:若任务内部捕获异常后未进行合理处理,可能导致关键业务流程中断或数据不一致,因此需要结合具体业务设计合适的异常处理逻辑。

9.2、通过Future获取异常

当使用submit()方法提交实现Callable接口的任务时,会返回一个Future对象。通过Future.get()方法可以阻塞等待任务执行结果,若任务执行过程中抛出异常,会被封装在ExecutionException中,可通过捕获该异常获取任务内部的异常信息。

Future<?> future = customPool.submit(() -> {
    // 模拟任务执行过程中抛出异常
    throw new RuntimeException("模拟异常");
});

try {
    // 阻塞等待任务执行完成并获取结果,若任务异常则抛出ExecutionException
    future.get();
} catch (InterruptedException e) {
    // 捕获线程中断异常
    Thread.currentThread().interrupt();
    System.err.println("线程被中断: " + e);
} catch (ExecutionException e) {
    // 捕获任务执行过程中抛出的异常
    System.err.println("捕获到任务异常: " + e.getCause());
}

说明Future.get()方法为阻塞操作,调用线程会一直等待直至任务完成。若任务执行耗时较长,建议设置超时参数,避免线程无限期阻塞。

9.3、全局异常处理器

通过自定义ThreadFactory并设置UncaughtExceptionHandler,可以为线程池中的所有线程统一设置全局异常处理器。当线程执行任务时抛出未捕获的异常,会由该处理器进行处理,这种方式适用于对异常进行集中监控和统一处理的场景。

ThreadFactory factory = r -> {
    Thread t = new Thread(r);
    // 设置未捕获异常处理器
    t.setUncaughtExceptionHandler((thread, ex) -> {
        System.err.println("全局捕获异常: " + ex);
        // 可添加更多处理逻辑,如记录详细日志、发送告警通知等
    });
    return t;
};

ThreadPoolExecutor pool = new ThreadPoolExecutor(
    2, 4, 30, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(),
    factory
);

优势:全局异常处理器能够确保所有未被任务内部捕获的异常都得到统一处理,避免异常导致线程终止或程序崩溃,提升系统的稳定性和可靠性。同时,集中处理异常也便于进行日志记录和问题排查。

三、与SpringBoot整合

在 Spring Boot 项目中,高效利用线程池能够显著提升系统的并发处理能力和响应性能。结合@Async注解与ThreadPoolTaskExecutor,可以便捷地实现异步任务调度,以下为详细的整合步骤与使用方法。

1、快速集成线程池

1.1、 启用异步支持

在 Spring Boot 应用的启动类或配置类上添加@EnableAsync注解,开启异步任务执行功能。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync // 开启异步支持,允许使用@Async注解执行异步任务
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

1.2、配置线程池参数

application.yml配置文件中,通过自定义属性来设置线程池的核心参数,确保线程池符合业务场景需求

async:
  thread-pool:
    core-size: 4               # 核心线程数,线程池长期维持的线程数量
    max-size: 8                # 最大线程数,线程池可容纳的最大线程数量
    queue-capacity: 100        # 任务队列容量,用于暂存等待执行的任务
    keep-alive: 60s            # 空闲线程存活时间,超过该时间的空闲线程将被销毁
    thread-name-prefix: async- # 线程名称前缀,便于在日志中识别线程所属线程池
    await-termination: 30s     # 线程池关闭时等待任务完成的最长时间
    wait-for-tasks: true       # 是否等待剩余任务执行完毕后再关闭线程池

1.3、自定义线程池配置类

创建配置类,将配置文件中的参数绑定到ThreadPoolTaskExecutor实例,并设置拒绝策略等属性。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadPoolExecutor;

@Configuration
@EnableAsync
public class ThreadPoolConfig {

    @Autowired
    private AsyncThreadPoolProperties properties;

    @Bean("taskExecutor")
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 从配置类中读取参数
        executor.setCorePoolSize(properties.getCoreSize());
        executor.setMaxPoolSize(properties.getMaxSize());
        executor.setQueueCapacity(properties.getQueueCapacity());
        executor.setKeepAliveSeconds((int) properties.getKeepAlive().getSeconds());
        executor.setThreadNamePrefix(properties.getThreadNamePrefix());
        executor.setWaitForTasksToCompleteOnShutdown(properties.isWaitForTasks());
        executor.setAwaitTerminationSeconds((int) properties.getAwaitTermination().getSeconds());
        // 设置拒绝策略:当任务队列已满且线程数达到最大值时,由调用者线程执行任务
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); 
        executor.initialize();
        return executor;
    }
}

:上述代码中AsyncThreadPoolProperties为自定义配置类,用于绑定application.yml中的线程池参数,需通过@ConfigurationProperties注解实现参数映射。

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.time.Duration;

@Data
@ConfigurationProperties(prefix = "async.thread-pool")
public class AsyncThreadPoolProperties {
    private int coreSize;
    private int maxSize;
    private int queueCapacity;
    private Duration keepAlive;
    private String threadNamePrefix;
    private Duration awaitTermination;
    private boolean waitForTasks;
}

2、使用线程池

2.1、使用@Async执行异步任务

  1. 编写异步服务:在业务Service类中,对需要异步执行的方法添加@Async注解,并指定线程池的Bean名称。

在 Spring Boot 中,若未显式配置线程池,@Async 默认使用 SimpleAsyncTaskExecutor(无界线程池)。自定义线程池时需注意:

  1. 配置类添加 @EnableAsync
  2. 显式指定 Bean 名称(如 @Bean("taskExecutor"))。
  3. 使用 @Async("taskExecutor") 绑定自定义线程池。
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;

@Service
public class OrderService {

    @Async("taskExecutor") // 指定使用名为taskExecutor的线程池执行异步任务
    public CompletableFuture<String> processOrderAsync(String orderId) {
        try {
            // 模拟耗时操作,如数据库查询、网络调用等
            Thread.sleep(1000); 
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return CompletableFuture.completedFuture("订单 " + orderId + " 处理完成");
    }
}
  1. 调用异步方法:在Controller或其他Service中调用异步方法,返回CompletableFuture对象,可通过该对象获取异步任务的执行结果。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.CompletableFuture;

@RestController
public class OrderController {

    @Autowired
    private OrderService orderService;

    @GetMapping("/process")
    public CompletableFuture<String> processOrder(@RequestParam String orderId) {
        return orderService.processOrderAsync(orderId);
    }
}
  1. 手动提交任务到线程池

通过依赖注入ThreadPoolTaskExecutor实例,直接调用其executesubmit方法提交任务。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@Service
public class ReportService {

    @Autowired
    private ThreadPoolTaskExecutor taskExecutor;

    public void generateReport() {
        taskExecutor.execute(() -> {
            // 编写生成报表的具体逻辑
            System.out.println("报表生成线程:" + Thread.currentThread().getName());
        });
    }

    //提交带返回值的任务
    public CompletableFuture<String> queryData() {
        return CompletableFuture.supplyAsync(() -> {
            // 执行查询数据库或调用外部接口等操作
            return "查询结果";
        }, taskExecutor);
    }
}

3、开发中常见的场景

3.1、异步任务处理

应用场景

适用于发送电子邮件、执行后台计算等无需同步等待结果,且不希望阻塞主程序执行的任务。将这类任务提交给线程池处理,主程序可继续执行后续逻辑,提升整体运行效率。

优势

充分利用线程资源,避免因同步执行耗时任务导致主线程阻塞,增强系统的响应能力和吞吐量。

3.2、并行数据处理

应用场景

当需要处理大量数据时,如从 MySQL 中取出 10 万条数据进行分批次处理,可借助线程池实现并行计算。将数据划分为多个批次,每个批次由线程池中的线程并行处理,最后合并结果。

示例代码

import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.stream.Collectors;

public class BatchDataProcessor {
    // 每批次处理的数据量
    private static final int BATCH_SIZE = 5;
    // 创建一个固定大小的线程池
    private final ExecutorService executorService = Executors.newFixedThreadPool(10);

    public void process(List<String> allDataList) {
        if (allDataList == null || allDataList.isEmpty()) {
            return;
        }
        // 将数据划分为多个批次
        List<List<String>> batchDatas = Lists.partition(allDataList, BATCH_SIZE);
        // 用于存储所有的CompletableFuture
        List<CompletableFuture<List<String>>> futureList = new ArrayList<>();

        for (List<String> batchData : batchDatas) {
            // 为每个批次的数据创建一个CompletableFuture任务,异步处理
            CompletableFuture<List<String>> future = CompletableFuture.supplyAsync(() -> processData(batchData), executorService);
            futureList.add(future);
        }

        // 等待所有CompletableFuture任务完成
        CompletableFuture<Void> allFutures = CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0]));
        // 合并所有批次的处理结果
        CompletableFuture<List<String>> allResultsFuture = allFutures.thenApply(v -> futureList.stream()
                .map(CompletableFuture::join)
                .flatMap(List::stream)
                .collect(Collectors.toList()));

        try {
            // 获取最终合并结果并处理
            List<String> result = allResultsFuture.get();
            for (String s : result) {
                System.out.println(s);
            }
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        } finally {
            // 关闭线程池,释放资源
            executorService.shutdown();
        }
    }

    // 处理数据的逻辑,为每条数据添加处理标识
    private List<String> processData(List<String> batchData) {
        if (batchData == null || batchData.isEmpty()) {
            return batchData;
        }
        int index = 0;
        for (String batchDatum : batchData) {
            batchData.set(index, batchDatum + "had process");
            index++;
        }
        // 模拟业务处理耗时
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return batchData;
    }

    public static void main(String[] args) {
        BatchDataProcessor batchDataProcessor = new BatchDataProcessor();
        // 构造测试数据
        List<String> allDataList = new ArrayList<>();
        for (int i = 0; i < 50; i++) {
            allDataList.add("data" + i);
        }
        batchDataProcessor.process(allDataList);
    }
}

关键逻辑说明

  1. 数据分批次:使用Lists.partition方法将原始数据划分为指定大小的批次。
  2. 任务提交:为每个批次创建CompletableFuture任务,提交给线程池异步处理。
  3. 结果合并:通过CompletableFuture.allOf等待所有任务完成,再使用流操作合并各批次结果。
  4. 资源释放:处理完毕后关闭线程池,避免资源浪费。

3.3、延时异步任务

应用场景

需要在指定延迟时间后执行异步任务的场景,例如定时清理缓存、定时发送提醒消息等。

传统方案问题

若使用Thread.sleep实现延迟任务,会造成线程资源浪费,在高并发情况下易导致线程资源紧缺,影响系统性能。

推荐方案

使用ScheduledExecutorService,它能在指定延迟时间后执行任务,期间不占用线程资源,更加高效优雅。

示例代码

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

public class AsyncTaskExample {
    public static void main(String[] args) {
        // 创建一个单线程的ScheduledExecutorService
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);

        //使用schedule方法提交任务,并指定延迟时间和时间单位
        // 提交一个30秒后执行的任务
        executorService.schedule(() -> {
            System.out.println("30秒已过,异步任务完成。");
        }, 30, TimeUnit.SECONDS);

        // 主线程继续执行其他任务
        System.out.println("主线程继续执行...");

        // 适时关闭执行器服务,释放资源
        executorService.shutdown();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值