package com.itmc.configuration;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import java.util.concurrent.*;
/**
* @Author: yhf
* @Date: 2026/4/13 9:33
*/
@Configuration
//@EnableAsync //@EnableAsync放在启动类(推荐)或配置类上,功能完全相同,都能开启异步。但是放在配置类时加载顺序晚于启动类,极端情况下会导致异步失效(比如配置类加载失败、被依赖影响)。
public class ThreadPoolConfig {
private static final Logger logger = LoggerFactory.getLogger(ThreadPoolConfig.class);
// 核心线程池大小
private int corePoolSize = 50;
// 最大可创建的线程数
private int maxPoolSize = 200;
// 队列最大长度
private int queueCapacity = 1000;
// 线程池维护线程所允许的空闲时间
private int keepAliveSeconds = 300;
//默认异步线程池:service层方法上+@Async或@Async("taskExecutor")
@Bean(name = "taskExecutor") //taskExecutor是Spring @Async默认查找的Bean名称,此处一旦定义,所有没指定名称的@Async都会用它
public Executor taskExecutor() {
ThreadPoolTaskExecutor poolTaskExecutor = new ThreadPoolTaskExecutor();
/*// 获取CPU核心数
int core = Runtime.getRuntime().availableProcessors();
// 设置核心线程数
poolTaskExecutor.setCorePoolSize(core - 1); //核心线程根据CPU核数动态设置,IO密集型常用
// 设置最大线程数
poolTaskExecutor.setMaxPoolSize(2 * core + 1);
// 允许线程的空闲时间,超过了核心线程数之外的线程,在空闲时间到达后会被销毁
poolTaskExecutor.setKeepAliveSeconds(60);
// 传入值大于1,底层队列使用的是LinkedBlockingQueue,默认为SynchronousQueue
poolTaskExecutor.setQueueCapacity(40);*/
// ======== 核心配置(优化后)========
poolTaskExecutor.setMaxPoolSize(20); // 最大20足够支撑高并发(峰值安全扩容)
poolTaskExecutor.setCorePoolSize(10); // IO密集型固定10,更稳定(业务异步任务常驻足够线程,不频繁创建)
poolTaskExecutor.setQueueCapacity(1000); // 队列放大,避免频繁扩容(抗流量冲击,不轻易触发最大线程)
poolTaskExecutor.setKeepAliveSeconds(60); // 空闲等待60秒
// 设置线程名称前缀,日志方便排查
poolTaskExecutor.setThreadNamePrefix("DefaultAsync-");
// 设置拒绝策略
// rejection-policy:当pool已经达到max size的时候,如何处理新任务
// CALLER_RUNS:不在新线程中执行任务,而是由调用者所在的线程来执行
poolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); //不丢任务、不抛异常
//优雅停机-->服务重启不丢任务
// 优雅停机设置1:设置线程池关闭的时候等待所有任务执行完毕在继续销毁
poolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
// 优雅停机设置2:线程池中的任务的等待时间,如果超出时间没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住
poolTaskExecutor.setAwaitTerminationSeconds(60);
// 初始化
poolTaskExecutor.initialize();
return poolTaskExecutor;
}
/*
@Bean
public Executor taskExecutor() { ... }
@Bean(name = "taskExecutor")
public Executor anyName() { ... }
上述两者效果一样
@Bean没写name时,Spring会自动把方法名taskExecutor作为Bean的名字
@Bean指定name时,方法名写啥都行,@Async会根据指定的name去找线程池
*/
//业务自定义线程池:service层方法上+@Async("threadPoolTaskExecutor")
@Bean(name = "threadPoolTaskExecutor")
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setMaxPoolSize(maxPoolSize); //最大可创建的线程数=200 [最大线程数,峰值时可创建]
executor.setCorePoolSize(corePoolSize); // 核心线程池大小=50 [核心线程数,常驻线程]
executor.setQueueCapacity(queueCapacity); // 队列最大长度=1000 [任务队列容量]
executor.setKeepAliveSeconds(keepAliveSeconds); // 线程池维护线程所允许的空闲时间=300 [非核心线程空闲存活时间(秒)]
executor.setThreadNamePrefix("CustomAsync-"); //线程名称前缀,便于日志追踪
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); //拒绝策略:调用者线程执行
/*
工作流程:新任务提交 → 核心线程(50) → 队列(1000) → 最大线程(200) → 拒绝策略
线程池对拒绝任务(无线程可用)的处理策略: ThreadPoolExecutor.CallerRunsPolicy()
不丢弃任务:不允许任务丢失,但可以接受任务处理延迟。确保提交的任务最终会被执行。
不抛异常:不会触发 RejectedExecutionException
背压效应:会降低任务提交的速度,因为调用者线程被阻塞去执行任务了。希望借此减缓任务提交速度,避免系统过载。
其他常见策略对比
AbortPolicy (默认):直接抛出 RejectedExecutionException 异常。
DiscardPolicy:直接丢弃新任务,不抛异常。
DiscardOldestPolicy:丢弃队列中最老的任务,尝试重新提交新任务。
*/
// executor.setWaitForTasksToCompleteOnShutdown(true); //优雅关闭配置1
// executor.setAwaitTerminationSeconds(60*3); //优雅关闭配置2
// executor.initialize(); //确保线程池正确初始化
return executor;
}
//资源隔离线程池:service层方法上+@Async("threadPoolResourceExchange")
//配置多个独立@Async线程池 --> 业务隔离,不互相拖垮
@Bean(name = "threadPoolResourceExchange")
public ThreadPoolTaskExecutor threadPoolResourceExchange() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setMaxPoolSize(20);
executor.setCorePoolSize(4);
executor.setQueueCapacity(200);
executor.setKeepAliveSeconds(keepAliveSeconds);
executor.setThreadNamePrefix("ResourceAsync-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
//调度器执行周期、定时、延迟任务(方案1:使用JDK原生)
//[如果你依赖 JDK 原生的 ScheduledExecutorService 接口,应继续使用 ScheduledThreadPoolExecutor。]
@Bean(name = "scheduledExecutorService")
protected ScheduledExecutorService scheduledExecutorService() {
return new ScheduledThreadPoolExecutor(corePoolSize,
new BasicThreadFactory.Builder().namingPattern("schedule-poolJDK-%d").daemon(true).build())
{
@Override
protected void afterExecute(Runnable r, Throwable t)
{
super.afterExecute(r, t);
printException(r, t);
}
};
/*
核心线程数 corePoolSize 需确保该变量已定义,决定常驻线程数
线程命名 schedule-pool-%d 便于日志追踪和问题排查
守护线程 daemon(true) 注意:JVM 退出时会强制终止,可能导致任务中断
异常处理 afterExecute 重写该方法通常用于捕获任务执行中的异常
*/
}
//调度器执行周期、定时、延迟任务(方案2:使用Spring封装)
@Bean(name = "scheduledExecutorService2")
protected ThreadPoolTaskScheduler scheduledExecutorService2() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(corePoolSize);
scheduler.setThreadNamePrefix("schedule-poolSpring-");
scheduler.setDaemon(false); // 关键任务建议非守护线程,防止任务意外中断。true时JVM关闭直接杀任务,false时任务不执行完,JVM不会强制关闭。
/*scheduler.setAwaitTerminationSeconds(60);
scheduler.setWaitForTasksToCompleteOnShutdown(true);
scheduler.initialize();*/
scheduler.setErrorHandler(t -> logger.error("定时/延迟任务执行异常", t)); // 如果需要全局异常处理,可配置 TaskErrorHandler
return scheduler;
}
/*
scheduledExecutorService2 = 万能任务调度器,四种用途:
1. 固定频率重复执行
// 每 10 秒执行一次(固定频率)
scheduledExecutorService2.scheduleAtFixedRate(() -> {
// 循环执行的业务
}, 0, 10, TimeUnit.SECONDS);
2. 固定间隔重复执行
// 上一次执行完 → 等 10 秒 → 再执行
scheduledExecutorService2.scheduleWithFixedDelay(() -> {
// 业务逻辑
}, 0, 10, TimeUnit.SECONDS);
3. 动态定时任务
// 指定未来某个时间执行
Date futureTime = ...; // 比如晚上8点
scheduledExecutorService2.schedule(() -> {
// 到时执行
}, futureTime);
4. 取消任务
ScheduledFuture<?> future = scheduledExecutorService2.schedule(..., time);
future.cancel(false); // 不想执行了,直接取消
@Autowired
private ThreadPoolTaskScheduler taskScheduler;
直接自动注入默认的 taskScheduler:
池大小 = 1(单线程)
线程名 = 系统默认
只能处理少量延迟任务
并发高一点 就会排队、阻塞、延迟不准
@Autowired
@Qualifier("scheduledExecutorService2") // 强制指定用你配置的Bean → 保险
private ThreadPoolTaskScheduler scheduledExecutorService2; // 变量名 = Bean名 → 精准匹配
自己配置(scheduledExecutorService2):
池大小 你自己设(比如 50)
线程名 你自己定义
守护线程、异常处理 你控制
适合大量延迟任务、生产环境
*/
/**
* 打印线程异常信息
*/
public static void printException(Runnable r, Throwable t)
{
if (t == null && r instanceof Future<?>)
{
try
{
Future<?> future = (Future<?>) r;
if (future.isDone())
{
future.get();
}
}
catch (CancellationException ce)
{
t = ce;
}
catch (ExecutionException ee)
{
t = ee.getCause();
}
catch (InterruptedException ie)
{
Thread.currentThread().interrupt();
}
}
if (t != null)
{
logger.error(t.getMessage(), t);
}
}
//@Async+Thread.sleep()=线程池杀手,会直接毁掉异步线程池的性能。(本地调试可以临时用,生产环境严禁出现)
//原因:线程池里的线程是复用的,不是无限创建。用sleep()会让线程空占着不干活,阻塞整个线程池。
//结果:线程池很快被占满,新的异步任务全部排队或卡住。服务吞吐量暴跌,甚至引发任务堆积或超时。
//解决:类@Autowired private ThreadPoolTaskScheduler taskScheduler;方法taskScheduler.schedule(()->{业务逻辑}, new Date(System.currentTimeMillis()+延迟时间毫秒));
//或者使用自定义bean更好 类@Autowired private ThreadPoolTaskScheduler scheduledExecutorService2;方法scheduledExecutorService2.schedule(()->{业务逻辑}, new Date(System.currentTimeMillis()+延迟时间毫秒));
//不阻塞@Async线程,等待期间线程直接放回线程池,延迟靠调度器ThreadPoolTaskScheduler实现,零资源浪费,生产环境安全。
//@Async + @Transactional 同时标在同一个方法上 = 事务失效!
//事务只在当前线程生效,异步会新开线程,线程之间不共享事务!
//正确写法:异步方法调用内部带事务的同步方法!
/*
@Service
public class YourService {
// ======================
// 异步入口:只负责异步
// ======================
@Async("taskExecutor")
public void doAsync() {
// 调用内部事务方法
this.doRealBiz();
}
// ======================
// 同步方法:只负责事务
// ======================
@Transactional(rollbackFor = Exception.class)
public void doRealBiz() {
// 数据库增删改 → 事务正常生效
}
}
*/
//不要在 @Transactional 里直接调用 @Async!
//会导致:异步方法读不到未提交数据,数据不一致,业务异常。【事务未提交 → 异步方法已执行 → 脏读、不可重复读】
//推荐等事务提交完成之后,再调用异步方法!
}