ThreadPoolTaskExecutor 类的使用介绍

一、核心概念:ThreadPoolTaskExecutor 是什么?

 
ThreadPoolTaskExecutor 是 Spring 框架 对 JDK 原生 ThreadPoolExecutor 的封装(属于 spring-context-support 模块),相比原生线程池,它:
 
  • 提供更简洁的配置方式(支持 Spring 风格的参数注入);
  • 内置线程池初始化、优雅关闭等通用逻辑;
  • 兼容 Spring 生态(如和 @Async 注解配合使用);
  • 补充了原生线程池缺少的便捷监控、参数校验等功能。
 
核心定位:Spring 项目中实现异步任务、线程池管理的首选工具,替代 JDK 原生 ThreadPoolExecutor 的硬编码配置,也规避了 Executors 工具类的缺陷。

二、核心配置参数(必掌握)

 
ThreadPoolTaskExecutor 的核心参数对应原生线程池的核心能力,每个参数都有明确的作用和配置原则:
 

image

 

三、完整使用步骤(Spring Boot 场景)

 

1. 依赖准备(Spring Boot 已内置,无需额外引入)

 
如果是非 Spring Boot 项目,需引入:
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>5.3.30</version>
</dependency>

2. 基础配置(两种方式)

方式 1:配置类硬编码(推荐,可控性高)
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

@Configuration
public class ThreadPoolConfig {

    @Bean("taskExecutor") // 给线程池命名,便于注入
    public Executor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

        // 1. 核心参数配置
        executor.setCorePoolSize(Runtime.getRuntime().availableProcessors() + 1); // 核心线程数
        executor.setMaxPoolSize(Runtime.getRuntime().availableProcessors() * 2);  // 最大线程数
        executor.setQueueCapacity(200); // 有界队列容量
        executor.setKeepAliveSeconds(60); // 非核心线程存活时间
        executor.setAllowCoreThreadTimeOut(false); // 核心线程不超时

        // 2. 自定义线程工厂(设置线程名称、异常处理器)
        executor.setThreadNamePrefix("biz-async-"); // 线程名称前缀:biz-async-1、biz-async-2...
        executor.setThreadFactory(runnable -> {
            Thread thread = new Thread(runnable, executor.getThreadNamePrefix() + System.currentTimeMillis());
            thread.setUncaughtExceptionHandler((t, e) -> {
                // 捕获线程内未处理的异常,避免线程静默退出
                System.err.println("线程" + t.getName() + "执行异常:" + e.getMessage());
            });
            return thread;
        });

        // 3. 拒绝策略(推荐使用 CallerRunsPolicy,由调用线程兜底执行)
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

        // 4. 初始化线程池(必须调用,否则线程池不生效)
        executor.initialize();

        return executor;
    }
}

方式 2:配置文件 + 注解(更灵活,支持动态调整)

# application.yml 配置
spring:
  task:
    execution:
      pool:
        core-size: 8          # 核心线程数
        max-size: 16          # 最大线程数
        queue-capacity: 200   # 队列容量
        keep-alive: 60s       # 空闲存活时间
        allow-core-thread-timeout: false # 核心线程不超时
      thread-name-prefix: "yml-async-" # 线程名称前缀
// 配置类读取yml配置
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
import org.springframework.boot.task.ThreadPoolTaskExecutorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;

@Configuration
public class YmlThreadPoolConfig {

    @Bean("ymlTaskExecutor")
    public ThreadPoolTaskExecutor ymlTaskExecutor(ThreadPoolTaskExecutorBuilder builder) {
        return builder
                .rejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()) // 覆盖默认拒绝策略
                .build();
    }
}

3. 核心使用方式(3 种常见场景)

场景 1:和 @Async 注解配合(最常用)
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class AsyncService {

    // 指定使用名称为"taskExecutor"的线程池
    @Async("taskExecutor")
    public void doAsyncTask1() {
        // 异步执行的业务逻辑
        System.out.println("任务1执行线程:" + Thread.currentThread().getName());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // 恢复中断状态
        }
    }

    @Async("ymlTaskExecutor")
    public void doAsyncTask2() {
        System.out.println("任务2执行线程:" + Thread.currentThread().getName());
    }
}
注意:使用 @Async 需在 Spring 启动类上添加 @EnableAsync 注解开启异步功能。
场景 2:手动提交任务(更灵活)
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;

@Service
public class ManualTaskService {

    // 注入自定义线程池
    @Resource
    @Qualifier("taskExecutor")
    private Executor taskExecutor;

    // 提交无返回值任务
    public void submitTask() {
        taskExecutor.execute(() -> {
            System.out.println("手动提交任务,线程:" + Thread.currentThread().getName());
        });
    }

    // 提交有返回值任务(需强转为 ThreadPoolTaskExecutor)
    public Future<String> submitTaskWithResult() {
        ThreadPoolTaskExecutor executor = (ThreadPoolTaskExecutor) taskExecutor;
        return executor.submit(() -> {
            Thread.sleep(1000);
            return "任务执行结果:" + Thread.currentThread().getName();
        });
    }
}
场景 3:优雅关闭线程池(避免资源泄漏)
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@Component
public class ThreadPoolShutdownConfig {

    @Resource
    @Qualifier("taskExecutor")
    private ThreadPoolTaskExecutor taskExecutor;

    // Spring容器销毁时执行(@PreDestroy)
    @PreDestroy
    public void shutdown() {
        if (taskExecutor != null) {
            // 1. 停止接收新任务
            taskExecutor.shutdown();
            try {
                // 2. 等待现有任务执行完成(设置超时时间)
                if (!taskExecutor.getThreadPoolExecutor().awaitTermination(30, java.util.concurrent.TimeUnit.SECONDS)) {
                    // 3. 超时则强制关闭(中断正在执行的任务)
                    taskExecutor.shutdownNow();
                }
            } catch (InterruptedException e) {
                // 4. 捕获中断异常,强制关闭
                taskExecutor.shutdownNow();
                Thread.currentThread().interrupt(); // 恢复中断状态
            }
        }
    }
}

 

四、关键功能与扩展

 

1. 线程池状态监控(排查问题必备)

 
ThreadPoolTaskExecutor 提供了获取线程池运行状态的方法,便于监控和排查问题:
// 获取原生 ThreadPoolExecutor
ThreadPoolExecutor nativeExecutor = taskExecutor.getThreadPoolExecutor();

// 核心监控指标
int corePoolSize = nativeExecutor.getCorePoolSize(); // 核心线程数
int activeCount = nativeExecutor.getActiveCount();   // 活跃线程数
long completedTaskCount = nativeExecutor.getCompletedTaskCount(); // 已完成任务数
int queueSize = nativeExecutor.getQueue().size();    // 队列等待任务数
boolean isShutdown = nativeExecutor.isShutdown();   // 是否已关闭

2. 常见问题与避坑点

 
  • 问题 1:线程池不生效 → 忘记调用 initialize() 方法(配置类中必须调用);
  • 问题 2:@Async 注解失效 → ① 注解加在非 public 方法上;② 同类内调用异步方法;③ 未加 @EnableAsync;
  • 问题 3:任务堆积导致 OOM → queueCapacity 未设置(默认无界),必须指定具体数值;
  • 问题 4:线程异常无日志 → 未设置 UncaughtExceptionHandler,线程内异常会静默丢失;
  • 问题 5:程序退出后线程池仍运行 → 未执行 shutdown(),非守护线程会阻止 JVM 退出。
 

3. 进阶用法:自定义任务装饰器(增强任务)

 
可通过 setTaskDecorator 给任务添加通用逻辑(如日志、耗时统计、上下文传递):
executor.setTaskDecorator(runnable -> () -> {
    // 执行任务前:记录开始时间、传递上下文(如MDC、用户信息)
    long start = System.currentTimeMillis();
    try {
        runnable.run(); // 执行原任务
    } finally {
        // 执行任务后:记录耗时、清理上下文
        long cost = System.currentTimeMillis() - start;
        System.out.println("任务执行耗时:" + cost + "ms");
    }
});

总结

 
  1. 核心定位:ThreadPoolTaskExecutor 是 Spring 对 JDK 原生线程池的封装,简化配置、兼容 Spring 生态,是 Spring 项目线程池的首选;
  2. 核心配置:重点配置 corePoolSize(核心线程数)、maxPoolSize(最大线程数)、queueCapacity(有界队列)、threadFactory(自定义线程名)、rejectedExecutionHandler(拒绝策略);
  3. 使用规范:① 和 @Async 配合实现异步任务;② 手动提交任务支持有返回值场景;③ 必须实现优雅关闭避免资源泄漏;④ 监控线程池状态便于排查问题。
 
 

如果需要结合具体业务场景(如批量任务处理、定时任务线程池)使用 ThreadPoolTaskExecutor,可以告诉我,我会补充对应的实战示例

ThreadPoolTaskExecutor 实现批量任务处理的具体方案,包括批量任务的提交、进度监控、异常处理、结果汇总等核心场景,我会给出完整的可落地代码和最佳实践。

 

一、批量任务处理的核心需求

 
批量任务处理通常需要解决这些问题:
 
  1. 批量提交大量子任务到线程池,避免一次性提交导致队列溢出;
  2. 监控所有子任务的执行进度(成功 / 失败 / 总数);
  3. 统一处理子任务异常,避免单个任务失败导致整体中断;
  4. 汇总所有任务的执行结果,支持成功 / 失败分类;
  5. 支持批量任务的暂停 / 终止(可选)。
 

二、完整实现方案(Spring Boot 场景)

 

1. 核心工具类:批量任务处理器

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 基于ThreadPoolTaskExecutor的批量任务处理器
 * 支持:批量提交、进度监控、异常处理、结果汇总
 */
@Slf4j
@Component
public class BatchTaskProcessor {

    // 注入自定义线程池(对应之前配置的taskExecutor)
    @Resource
    @Qualifier("taskExecutor")
    private ThreadPoolTaskExecutor taskExecutor;

    // 批量任务结果封装
    public static class BatchTaskResult<T> {
        private T data; // 任务执行结果
        private boolean success; // 是否成功
        private String errorMsg; // 错误信息
        private long costTime; // 耗时(毫秒)

        // 构造器、getter/setter
        public BatchTaskResult(T data, boolean success, String errorMsg, long costTime) {
            this.data = data;
            this.success = success;
            this.errorMsg = errorMsg;
            this.costTime = costTime;
        }

        // 快速创建成功结果
        public static <T> BatchTaskResult<T> success(T data, long costTime) {
            return new BatchTaskResult<>(data, true, null, costTime);
        }

        // 快速创建失败结果
        public static <T> BatchTaskResult<T> fail(String errorMsg, long costTime) {
            return new BatchTaskResult<>(null, false, errorMsg, costTime);
        }

        // getter/setter 省略(实际开发中用lombok的@Data)
        public T getData() { return data; }
        public boolean isSuccess() { return success; }
        public String getErrorMsg() { return errorMsg; }
        public long getCostTime() { return costTime; }
    }

    /**
     * 执行批量任务(核心方法)
     * @param taskList 待执行的子任务列表
     * @param batchSize 每次提交的批次大小(避免一次性提交过多任务)
     * @param timeout 整体超时时间(秒)
     * @return 所有任务的执行结果
     */
    public <T> List<BatchTaskResult<T>> executeBatchTasks(
            List<Runnable> taskList,
            int batchSize,
            int timeout) {

        // 1. 初始化监控指标
        AtomicInteger successCount = new AtomicInteger(0);
        AtomicInteger failCount = new AtomicInteger(0);
        int totalCount = taskList.size();
        List<BatchTaskResult<T>> allResults = new ArrayList<>(totalCount);
        List<CompletableFuture<Void>> futureList = new ArrayList<>();

        log.info("开始执行批量任务,总任务数:{},批次大小:{},超时时间:{}秒",
                totalCount, batchSize, timeout);

        // 2. 分批次提交任务
        for (int i = 0; i < totalCount; i += batchSize) {
            // 截取当前批次的任务(避免越界)
            int end = Math.min(i + batchSize, totalCount);
            List<Runnable> batchTasks = taskList.subList(i, end);

            // 提交当前批次的所有任务
            for (Runnable task : batchTasks) {
                int finalI = i; // 闭包引用
                CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                    long start = System.currentTimeMillis();
                    try {
                        // 执行子任务
                        task.run();
                        // 记录成功结果
                        allResults.add((BatchTaskResult<T>) BatchTaskResult.success(
                                "任务" + finalI + "执行成功",
                                System.currentTimeMillis() - start));
                        successCount.incrementAndGet();
                    } catch (Exception e) {
                        // 捕获所有异常,避免影响其他任务
                        String errorMsg = "任务" + finalI + "执行失败:" + e.getMessage();
                        log.error(errorMsg, e);
                        allResults.add((BatchTaskResult<T>) BatchTaskResult.fail(
                                errorMsg,
                                System.currentTimeMillis() - start));
                        failCount.incrementAndGet();
                    } finally {
                        // 打印实时进度
                        log.info("批量任务进度:成功{} / 失败{} / 总数{}",
                                successCount.get(), failCount.get(), totalCount);
                    }
                }, taskExecutor);

                futureList.add(future);
            }

            // 可选:批次间短暂休眠,避免线程池瞬间压满
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                log.error("批次提交中断", e);
                break;
            }
        }

        // 3. 等待所有任务完成(带超时)
        try {
            CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0]))
                    .get(timeout, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            log.error("批量任务被中断", e);
            Thread.currentThread().interrupt();
        } catch (ExecutionException e) {
            log.error("批量任务执行异常", e.getCause());
        } catch (TimeoutException e) {
            log.error("批量任务超时({}秒),部分任务未完成", timeout);
            // 超时后取消未完成的任务
            futureList.forEach(future -> future.cancel(true));
        }

        // 4. 打印最终统计结果
        log.info("批量任务执行完成,总任务数:{},成功:{},失败:{}",
                totalCount, successCount.get(), failCount.get());

        return allResults;
    }

    /**
     * 简化版:无批次限制,直接提交所有任务
     */
    public <T> List<BatchTaskResult<T>> executeBatchTasks(List<Runnable> taskList, int timeout) {
        return executeBatchTasks(taskList, 50, timeout); // 默认批次大小50
    }
}
 

 2. 业务层使用示例(批量处理数据)

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

@Slf4j
@Service
public class BatchBizService {

    @Resource
    private BatchTaskProcessor batchTaskProcessor;

    /**
     * 示例:批量处理用户数据
     * @param userIds 待处理的用户ID列表
     */
    public void batchProcessUserData(List<Long> userIds) {
        // 1. 构建子任务列表
        List<Runnable> taskList = new ArrayList<>();
        for (Long userId : userIds) {
            taskList.add(() -> {
                // 单个用户的处理逻辑(示例:模拟数据库操作/接口调用)
                log.info("开始处理用户ID:{},线程:{}", userId, Thread.currentThread().getName());
                
                // 模拟业务耗时
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    throw new RuntimeException("任务被中断:" + userId, e);
                }

                // 模拟随机失败(便于测试异常处理)
                if (userId % 10 == 0) {
                    throw new RuntimeException("用户ID" + userId + "处理失败(模拟异常)");
                }

                log.info("完成处理用户ID:{}", userId);
            });
        }

        // 2. 执行批量任务(批次大小30,超时300秒)
        List<BatchTaskProcessor.BatchTaskResult<Object>> results = 
                batchTaskProcessor.executeBatchTasks(taskList, 30, 300);

        // 3. 汇总结果(成功/失败分类)
        List<String> successTasks = new ArrayList<>();
        List<String> failTasks = new ArrayList<>();
        for (BatchTaskProcessor.BatchTaskResult<Object> result : results) {
            if (result.isSuccess()) {
                successTasks.add(result.getData().toString());
            } else {
                failTasks.add(result.getErrorMsg());
            }
        }

        // 4. 输出汇总结果
        log.info("批量处理用户数据汇总:");
        log.info("成功数:{},失败数:{}", successTasks.size(), failTasks.size());
        if (!failTasks.isEmpty()) {
            log.warn("失败任务详情:{}", failTasks);
            // 可选:失败任务重试/告警
        }
    }
}

3. 控制器调用示例

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

@RestController
@RequestMapping("/batch")
public class BatchTaskController {

    @Resource
    private BatchBizService batchBizService;

    @GetMapping("/process")
    public String processBatch() {
        // 模拟100个用户ID
        List<Long> userIds = new ArrayList<>();
        for (long i = 1; i <= 100; i++) {
            userIds.add(i);
        }

        // 执行批量任务
        batchBizService.batchProcessUserData(userIds);
        return "批量任务已启动,可查看日志获取执行结果";
    }
}

三、进阶优化:批量任务的暂停 / 终止

 
如果需要支持批量任务的暂停和终止,可添加以下扩展:
// 在BatchTaskProcessor中添加
private volatile boolean isPause = false; // 暂停标记
private volatile boolean isStop = false; // 终止标记

/**
 * 暂停批量任务
 */
public void pauseBatchTask() {
    isPause = true;
    log.info("批量任务已暂停");
}

/**
 * 恢复批量任务
 */
public void resumeBatchTask() {
    isPause = false;
    log.info("批量任务已恢复");
}

/**
 * 终止批量任务
 */
public void stopBatchTask() {
    isStop = true;
    // 强制关闭线程池(谨慎使用,会中断正在执行的任务)
    taskExecutor.shutdownNow();
    log.info("批量任务已终止,线程池已强制关闭");
}

// 在子任务执行前添加判断
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    // 检查是否终止
    if (isStop) {
        log.warn("任务{}已终止,无需执行", finalI);
        return;
    }
    // 检查是否暂停
    while (isPause) {
        try {
            Thread.sleep(1000); // 暂停期间每秒检查一次
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return;
        }
    }
    // 原有任务执行逻辑...
}, taskExecutor);

四、批量任务处理的最佳实践

 
  1. 批次大小控制:
     
    • 批次大小建议设置为线程池最大线程数的 2~3 倍(如最大线程数 16,批次大小 30~50);
    • 避免批次过小(频繁提交,性能损耗)或过大(队列溢出,OOM 风险)。
     
  2. 超时设置:
     
    • 整体超时时间需根据单任务耗时 * 任务数估算,预留一定缓冲;
    • 子任务内部也可添加单独超时(如数据库操作、接口调用)。
     
  3. 异常处理:
     
    • 子任务必须捕获所有异常,避免单个任务失败导致CompletableFuture异常;
    • 失败任务建议记录到数据库 / 日志,支持重试(可结合定时任务实现自动重试)。
     
  4. 资源管控:
     
    • 批量处理数据库数据时,建议使用分页查询,避免一次性加载大量数据到内存;
    • 线程池核心线程数根据任务类型调整(IO 密集型可设为 CPU 核心数 * 2,CPU 密集型设为 CPU 核心数 + 1)。
     
  5. 监控告警:
     
    • 监控线程池的活跃线程数、队列长度、拒绝任务数;
    • 批量任务失败率超过阈值时,触发邮件 / 钉钉告警。
     
 

总结

 
  1. 核心实现:基于 ThreadPoolTaskExecutor + CompletableFuture 实现批量任务的分批次提交、进度监控和结果汇总,解决一次性提交大量任务导致的队列溢出问题;
  2. 关键优化:通过分批次提交控制线程池压力,统一异常处理避免整体中断,支持进度监控和结果分类汇总;
  3. 扩展能力:可按需添加暂停 / 终止、失败重试、监控告警等功能,适配不同业务场景的批量处理需求。

 

基于 ThreadPoolTaskExecutor(或 Spring 生态的定时任务线程池)实现定时任务的线程池配置与管理,核心是解决定时任务的并发控制、任务堆积、异常处理、资源泄漏等问题,我会给出完整的配置方案和实战示例。

 

一、定时任务线程池的核心痛点

 
Spring 默认的定时任务(@Scheduled)使用单线程执行,存在以下问题:
 
  1. 单线程处理所有定时任务,一个任务阻塞会导致后续所有任务延迟;
  2. 无任务队列限制,定时任务堆积易导致 OOM;
  3. 缺少自定义线程命名、拒绝策略,排查问题困难;
  4. 定时任务异常未处理时,会导致任务静默失败且线程无法回收。
 

二、核心方案:自定义定时任务线程池

 
Spring 提供 ScheduledExecutorService 适配定时任务,我们基于 ThreadPoolTaskScheduler(Spring 对定时线程池的封装,底层是 ScheduledThreadPoolExecutor)实现自定义配置,替代默认单线程池。
 

1. 定时任务线程池配置类

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 定时任务线程池配置
 * 替代Spring默认单线程,解决定时任务并发、阻塞、堆积问题
 */
@Slf4j
@Configuration
@EnableScheduling // 开启定时任务功能
public class ScheduledTaskPoolConfig {

    /**
     * 自定义定时任务线程池
     */
    @Bean(name = "scheduledTaskScheduler")
    public ThreadPoolTaskScheduler scheduledTaskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();

        // 1. 核心线程数(定时任务核心线程建议固定,避免频繁创建销毁)
        // 建议值:定时任务数量 + 2(预留冗余)
        scheduler.setPoolSize(10);

        // 2. 线程名称前缀(便于日志排查)
        scheduler.setThreadNamePrefix("scheduled-task-");

        // 3. 线程空闲存活时间(定时任务核心线程常驻,该参数仅对非核心线程生效)
        scheduler.setKeepAliveSeconds(60);

        // 4. 等待任务完成后再关闭线程池(默认false,建议设为true)
        scheduler.setWaitForTasksToCompleteOnShutdown(true);

        // 5. 线程池关闭时的等待超时时间(秒)
        scheduler.setAwaitTerminationSeconds(30);

        // 6. 拒绝策略(定时任务推荐使用CallerRunsPolicy,兜底执行)
        RejectedExecutionHandler rejectedHandler = new ThreadPoolExecutor.CallerRunsPolicy() {
            @Override
            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                log.error("定时任务线程池已满,任务被拒绝执行!任务:{},线程池状态:核心数{},活跃数{},队列数{}",
                        r.toString(),
                        executor.getCorePoolSize(),
                        executor.getActiveCount(),
                        executor.getQueue().size());
                // 兜底执行(由调用线程执行,避免任务丢失)
                super.rejectedExecution(r, executor);
            }
        };
        scheduler.setRejectedExecutionHandler(rejectedHandler);

        // 7. 线程池异常处理器(捕获定时任务未处理的异常)
        scheduler.setErrorHandler(throwable -> 
                log.error("定时任务执行异常", throwable)
        );

        // 初始化线程池
        scheduler.initialize();
        log.info("定时任务线程池初始化完成,核心线程数:{}", scheduler.getPoolSize());
        return scheduler;
    }
}

2. 定时任务使用示例(指定自定义线程池)

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.ScheduledFuture;

/**
 * 基于自定义线程池的定时任务示例
 */
@Slf4j
@Component
public class CustomScheduledTask {

    // 注入自定义定时线程池
    @Resource
    private ThreadPoolTaskScheduler scheduledTaskScheduler;

    // 保存定时任务的Future对象(用于动态取消任务)
    private ScheduledFuture<?> dynamicTaskFuture;

    // ==================== 方式1:注解式(指定线程池) ====================
    /**
     * 固定频率执行(每10秒执行一次,指定使用自定义线程池)
     * @Scheduled 注解的线程池指定:通过 @SchedulerLock 或直接绑定(Spring 4.3+支持)
     * 注:Spring默认会将所有@Scheduled任务绑定到该线程池(因配置了唯一的ThreadPoolTaskScheduler Bean)
     */
    @Scheduled(fixedRate = 10000, initialDelay = 5000)
    public void fixedRateTask() {
        log.info("注解式定时任务(固定频率)执行,线程:{}", Thread.currentThread().getName());
        // 模拟业务耗时(如批量处理数据)
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("定时任务被中断", e);
        }
    }

    /**
     * Cron表达式执行(每分钟第30秒执行)
     */
    @Scheduled(cron = "30 * * * * ?")
    public void cronTask() {
        log.info("注解式定时任务(Cron)执行,线程:{}", Thread.currentThread().getName());
    }

    // ==================== 方式2:编程式(动态创建/取消) ====================
    /**
     * 动态启动定时任务
     */
    public void startDynamicTask() {
        // 取消已有任务(避免重复)
        if (dynamicTaskFuture != null && !dynamicTaskFuture.isCancelled()) {
            dynamicTaskFuture.cancel(true);
        }

        // 动态创建定时任务(每5秒执行一次)
        dynamicTaskFuture = scheduledTaskScheduler.scheduleAtFixedRate(() -> {
            log.info("编程式动态定时任务执行,线程:{}", Thread.currentThread().getName());
            // 业务逻辑
        }, 5000);
        log.info("动态定时任务已启动");
    }

    /**
     * 动态取消定时任务
     */
    public void stopDynamicTask() {
        if (dynamicTaskFuture != null) {
            boolean isCancelled = dynamicTaskFuture.cancel(true);
            log.info("动态定时任务取消结果:{}", isCancelled);
        }
    }
}

3. 控制器调用(动态管理定时任务)

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;

@RestController
@RequestMapping("/scheduled")
public class ScheduledTaskController {

    @Resource
    private CustomScheduledTask customScheduledTask;

    /**
     * 启动动态定时任务
     */
    @GetMapping("/start")
    public String startDynamicTask() {
        customScheduledTask.startDynamicTask();
        return "动态定时任务已启动";
    }

    /**
     * 停止动态定时任务
     */
    @GetMapping("/stop")
    public String stopDynamicTask() {
        customScheduledTask.stopDynamicTask();
        return "动态定时任务已停止";
    }
}

三、定时任务线程池的关键优化点

 

1. 任务并发控制(避免重复执行)

 
定时任务可能因执行耗时超过间隔时间导致重复执行,解决方案:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Component
public class ConcurrentSafeScheduledTask {
    // 任务锁(保证同一任务同一时间只有一个实例执行)
    private final Lock taskLock = new ReentrantLock();

    @Scheduled(fixedRate = 5000)
    public void concurrentSafeTask() {
        // 尝试获取锁(非阻塞,避免任务堆积)
        if (!taskLock.tryLock()) {
            log.warn("定时任务仍在执行中,本次跳过");
            return;
        }

        try {
            log.info("并发安全的定时任务执行,线程:{}", Thread.currentThread().getName());
            // 模拟长耗时任务
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            // 必须释放锁
            taskLock.unlock();
        }
    }
}

2. 定时任务异常处理

 
避免定时任务因未捕获异常导致线程终止:
@Scheduled(fixedRate = 10000)
public void exceptionHandledTask() {
    try {
        log.info("带异常处理的定时任务执行");
        // 模拟异常
        int a = 1 / 0;
    } catch (Exception e) {
        log.error("定时任务执行异常,已捕获", e);
        // 可选:告警、重试
    }
}

3. 分布式定时任务(避免多实例重复执行)

 
如果是分布式部署,需添加分布式锁(以 Redisson 为例):
<!-- 引入Redisson依赖 -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.23.3</version>
</dependency>
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

@Component
public class DistributedScheduledTask {

    @Resource
    private RedissonClient redissonClient;

    private static final String TASK_LOCK_KEY = "scheduled:task:distributed:lock";

    @Scheduled(cron = "0 0/10 * * * ?")
    public void distributedTask() {
        RLock lock = redissonClient.getLock(TASK_LOCK_KEY);
        try {
            // 尝试获取锁(最多等待5秒,锁自动过期10秒)
            if (lock.tryLock(5, 10, TimeUnit.SECONDS)) {
                log.info("分布式定时任务执行,线程:{}", Thread.currentThread().getName());
                // 业务逻辑
            } else {
                log.warn("分布式锁获取失败,其他实例正在执行该任务");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("获取分布式锁中断", e);
        } finally {
            // 仅释放当前线程持有的锁
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

四、定时任务线程池 vs 普通线程池

image

 

总结

  1. 核心配置:定时任务线程池首选 ThreadPoolTaskScheduler,核心参数需配置 poolSize(核心线程数)、threadNamePrefix(线程命名)、rejectedExecutionHandler(拒绝策略)、waitForTasksToCompleteOnShutdown(优雅关闭);
  2. 使用方式:注解式(@Scheduled)适合固定规则的定时任务,编程式适合动态创建 / 取消的任务;
  3. 关键优化:添加并发锁避免任务重复执行,捕获所有异常防止线程终止,分布式场景需加分布式锁,拒绝策略优先选择 CallerRunsPolicy 兜底。
posted @ 2026-02-02 15:43  ConfidentLiu  阅读(443)  评论(0)    收藏  举报