ThreadPoolTaskExecutor 类的使用介绍
一、核心概念:ThreadPoolTaskExecutor 是什么?
ThreadPoolTaskExecutor 是 Spring 框架 对 JDK 原生 ThreadPoolExecutor 的封装(属于 spring-context-support 模块),相比原生线程池,它:- 提供更简洁的配置方式(支持 Spring 风格的参数注入);
- 内置线程池初始化、优雅关闭等通用逻辑;
- 兼容 Spring 生态(如和
@Async注解配合使用); - 补充了原生线程池缺少的便捷监控、参数校验等功能。
核心定位:Spring 项目中实现异步任务、线程池管理的首选工具,替代 JDK 原生
ThreadPoolExecutor 的硬编码配置,也规避了 Executors 工具类的缺陷。二、核心配置参数(必掌握)
ThreadPoolTaskExecutor 的核心参数对应原生线程池的核心能力,每个参数都有明确的作用和配置原则:
三、完整使用步骤(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"); } });
总结
- 核心定位:
ThreadPoolTaskExecutor是 Spring 对 JDK 原生线程池的封装,简化配置、兼容 Spring 生态,是 Spring 项目线程池的首选; - 核心配置:重点配置
corePoolSize(核心线程数)、maxPoolSize(最大线程数)、queueCapacity(有界队列)、threadFactory(自定义线程名)、rejectedExecutionHandler(拒绝策略); - 使用规范:① 和
@Async配合实现异步任务;② 手动提交任务支持有返回值场景;③ 必须实现优雅关闭避免资源泄漏;④ 监控线程池状态便于排查问题。
如果需要结合具体业务场景(如批量任务处理、定时任务线程池)使用 ThreadPoolTaskExecutor,可以告诉我,我会补充对应的实战示例
ThreadPoolTaskExecutor 实现批量任务处理的具体方案,包括批量任务的提交、进度监控、异常处理、结果汇总等核心场景,我会给出完整的可落地代码和最佳实践。
一、批量任务处理的核心需求
批量任务处理通常需要解决这些问题:
- 批量提交大量子任务到线程池,避免一次性提交导致队列溢出;
- 监控所有子任务的执行进度(成功 / 失败 / 总数);
- 统一处理子任务异常,避免单个任务失败导致整体中断;
- 汇总所有任务的执行结果,支持成功 / 失败分类;
- 支持批量任务的暂停 / 终止(可选)。
二、完整实现方案(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);
四、批量任务处理的最佳实践
-
批次大小控制:
- 批次大小建议设置为线程池最大线程数的 2~3 倍(如最大线程数 16,批次大小 30~50);
- 避免批次过小(频繁提交,性能损耗)或过大(队列溢出,OOM 风险)。
-
超时设置:
- 整体超时时间需根据单任务耗时 * 任务数估算,预留一定缓冲;
- 子任务内部也可添加单独超时(如数据库操作、接口调用)。
-
异常处理:
- 子任务必须捕获所有异常,避免单个任务失败导致
CompletableFuture异常; - 失败任务建议记录到数据库 / 日志,支持重试(可结合定时任务实现自动重试)。
- 子任务必须捕获所有异常,避免单个任务失败导致
-
资源管控:
- 批量处理数据库数据时,建议使用分页查询,避免一次性加载大量数据到内存;
- 线程池核心线程数根据任务类型调整(IO 密集型可设为 CPU 核心数 * 2,CPU 密集型设为 CPU 核心数 + 1)。
-
监控告警:
- 监控线程池的活跃线程数、队列长度、拒绝任务数;
- 批量任务失败率超过阈值时,触发邮件 / 钉钉告警。
总结
- 核心实现:基于
ThreadPoolTaskExecutor+CompletableFuture实现批量任务的分批次提交、进度监控和结果汇总,解决一次性提交大量任务导致的队列溢出问题; - 关键优化:通过分批次提交控制线程池压力,统一异常处理避免整体中断,支持进度监控和结果分类汇总;
- 扩展能力:可按需添加暂停 / 终止、失败重试、监控告警等功能,适配不同业务场景的批量处理需求。
基于 ThreadPoolTaskExecutor(或 Spring 生态的定时任务线程池)实现定时任务的线程池配置与管理,核心是解决定时任务的并发控制、任务堆积、异常处理、资源泄漏等问题,我会给出完整的配置方案和实战示例。
一、定时任务线程池的核心痛点
Spring 默认的定时任务(
@Scheduled)使用单线程执行,存在以下问题:- 单线程处理所有定时任务,一个任务阻塞会导致后续所有任务延迟;
- 无任务队列限制,定时任务堆积易导致 OOM;
- 缺少自定义线程命名、拒绝策略,排查问题困难;
- 定时任务异常未处理时,会导致任务静默失败且线程无法回收。
二、核心方案:自定义定时任务线程池
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 普通线程池

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

浙公网安备 33010602011771号