在应用中经常会遇到定时执行任务的需求,这时采用异步的方式开启一个定时任务,通常引用@Async注解,但直接使用会有风险,当我们没有指定线程池时,会默认使用其Spring自带的 SimpleAsyncTaskExecutor 线程池,会不断的创建线程,当并发大的时候会严重影响性能。
1. Introduction to @Async
@Async is an annotation. Its function is that the class or method with this annotation can perform tasks asynchronously. When this annotation is added to a method, it means that the method is an asynchronous method. When it is added to a class, it means that the method is an asynchronous method. All methods in the class are asynchronous methods.
When using @Async, if you do not specify a specific thread pool name, the default thread pool SimpleAsyncTaskExecutor is used. The default configuration of the thread pool is (in TaskExecutionProperties):
- Number of core threads: 8
- Capacity: Integer.MAX_VALUE
- Maximum number of threads: Integer.MAX_VALUE
- Idle thread survival time: 60s
- Allow core thread timeouts: true
It can be seen from the above configuration that when the amount of concurrency is large, threads will be created without limit. When the number of threads reaches a certain level, the corresponding performance will be affected. Therefore, when using the @Async annotation, it is best to use a custom thread pool, that is, add the name of the custom thread pool to the annotation.
2. Turn on asynchronous
Add @EnableAsync to the startup class to enable asynchronous, similar to the following:
@EnableAsync
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
3. Configure a custom thread pool
The configuration class is similar to the following:
@EnableAsync
@Configuration
public class ThreadPoolConfig {
/**
* 线程池任务执行器
* @return
*/
@Bean
public Executor taskExecutor() {
// 创建线程池
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 设置核心池大小
executor.setCorePoolSize(5);
// 设置最大池大小,只有在缓冲队列满了之后才会申请超过核心线程数的线程
executor.setMaxPoolSize(10);
// 设置队列容量
executor.setQueueCapacity(100);
// 设置保持活动秒数,当超过了核心线程数之外的线程在空闲时间到达之后会被销毁
executor.setKeepAliveSeconds(60);
// 设置线程名称前缀
executor.setThreadNamePrefix("myThread-");
// 设置拒绝的执行处理策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
}
There are generally four types of rejection strategies:
ThreadPoolExecutor.AbortPolicy: Discard the task and throw RejectedExecutionException.
ThreadPoolExecutor.DiscardPolicy: also discards tasks, but does not throw an exception.
ThreadPoolExecutor.DiscardOldestPolicy: Discard the front task of the queue, and then try to execute the task again (repeat this process)
ThreadPoolExecutor.CallerRunsPolicy: Retry adding the current task, Automatically call the execute() method repeatedly until successful
Note: @EnableAsync needs to be added to the startup class or configuration class to enable asynchronous.
The operating mechanism of the thread pool is as follows:
4. @Async specifies the thread pool
Just specify the thread pool name directly in the @Async annotation, similar to the following:
@Async("taskExecutor")
public void asyncTask() {
// 逻辑代码...
log.info("当前线程为:" + Thread.currentThread().getName());
}
5. Possible reasons for @Async failure
- The startup class or thread pool configuration class is not annotated with @EnableAsync
- Do not call the method and the @Async method in the same class
- The return value of the @Async method can only be void and Future
- Methods modified with @Async cannot be modified with static
- The method decorated with @Async must be public