1.配置文件
@Configuration
@EnableAsync
public class AsyncExecutorConfig {
@Value("${thread.number}")
private Integer threadNumber;
@Bean("asyncThreadExecutor")
public Executor asyncThreadExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 设置核心线程数
executor.setCorePoolSize(threadNumber);
// 设置线程队列大小
executor.setQueueCapacity(threadNumber * 10);
// 设置线程最大线程数量
executor.setMaxPoolSize(threadNumber * 5);
// 设置最大线程空闲时间,达到最大空闲时间则自动销毁
executor.setKeepAliveSeconds(30);
// 设置核心线程达到最大空闲时间时也可以销毁
executor.setAllowCoreThreadTimeOut(true);
/*
* rejection-policy:当pool已经达到max size的时候,如何处理新任务
* CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
*/
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 初始化
executor.initialize();
return executor;
}
}
- 各个参数说明可以查看这篇博客:https://blog.csdn.net/zhouhl_cn/article/details/7392607
2.异步线程使用
- 使用@Async注解配置即可,在调用当前方法是,Spring会自动使用多线程异步执行
- 使用注意事项:
- 异步方法使用static修饰
- 异步类没有使用@Component注解(或其他注解)导致spring无法扫描到异步类
- 异步方法不能与被调用的异步方法在同一个类中
@Async
public void asyncRun(){
System.out.println("异步线程启动了");
}
2.1.监听异步返回值
Future<T>
: 使用Future作为返回值
// 异步方法
import java.util.concurrent.Future;
public class TestDemo1{
@Async
public Future<String> asyncRun() {
System.out.println("异步线程启动了");
return new AsyncResult<>("");
}
}
// 异步方法调用
public void runTest() {
Future<String> future = testDemo1.asyncRun();
while (future.isDone()) {
log.info(“异步线程等待中”);
}
}
3.参数设置
Spring线程池默认值
# 核心线程数
- corePoolSize=1
# 队列长度
- queueCapacity=Integer.MAX_VALUE
# 最大线程数
- maxPoolSize=Integer.MAX_VALUE
# 线程池中空闲线程等待工作的超时时间,超过时间就会被销毁
- keepAliveTime=60s
# 是否开启核心线程超时后被关闭
- allowCoreThreadTimeout=false
# 对于队列达到最大长度并且已达到最大线程数的任务,进行直接丢弃
- rejectedExecutionHandler=AbortPolicy()
参数说明:
tasks :每秒的任务数,假设为100~1000
taskcost:每个任务花费时间,假设为0.1
sresponsetime:系统允许容忍的最大响应时间,假设为1s
参数优化建议:
- corePoolSize = tasks/(1/taskcost) =tasks*taskcout = (100~1000)*0.1 = 10~100 个线程。
- 根据8020原则,如果80%的每秒任务数小于400,那么corePoolSize设置为40即可
- queueCapacity = (coreSizePool/taskcost)*responsetime
- 计算可得 queueCapacity = 40/0.1*1 = 400。意思是队列里的线程可以等待1s,超过了的需要新开线程来执行
- 切记不能使用默认值,这样队列会很大,线程数只会保持在corePoolSize大小,当任务陡增时,不能新开线程来执行,响应时间会随之陡增。
- maxPoolSize = (max(tasks)- queueCapacity)/(1/taskcost)
- 最大线程数 =(最大任务数-队列容量)/每个线程每秒处理能力
- 计算可得 maxPoolSize = (1000-400)/10 = 60
- rejectedExecutionHandler:根据具体情况来决定,任务不重要可丢弃,任务重要则要利用一些缓冲机制来处理
- keepAliveTime和allowCoreThreadTimeout采用默认通常能满足
4.线程执行顺序
-
当线程数小于核心线程数时,创建线程.
-
当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列;
-
当线程数大于等于核心线程数,且任务队列已满.
- 若线程数小于最大线程数,创建线程;
- 若线程数等于最大线程数,抛出异常,拒绝任务;