1. 简单介绍
Spring异步线程池的接口类,其实质是java.util.concurrent.Executor
Spring 已经实现的异常线程池:
SimpleAsyncTaskExecutor
:不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。SyncTaskExecutor
:这个类没有实现异步调用,只是一个同步操作。只适用于不需要多线程的地方ConcurrentTaskExecutor
:Executor的适配类,不推荐使用。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类SimpleThreadPoolTaskExecutor
:是Quartz的SimpleThreadPool的类。线程池同时被quartz和非quartz使用,才需要使用此类ThreadPoolTaskExecutor
:最常使用,推荐!!! 其实质是对java.util.concurrent.ThreadPoolExecutor的包装
如果你想要异步处理,则需在对应的方法上加上注解**@Async**,则会启动一个新的线程去执行。
2. 异步配置方式
SpringBoot中开启异步支持非常简单,只需要在配置类上面加上注解 @EnableAsync
,同时定义自己的线程池即可。 也可以不定义自己的线程池,则使用系统默认的线程池。
这里推荐两种异步配置的方式:
方式1: 定义配置类,实现AsyncConfigurerc接口
@Configuration
@EnableAsync
public class AsyncConfiguration implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(100);
executor.setQueueCapacity(100);
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60 * 10);
executor.setThreadNamePrefix("async");
executor.initialize(); //需要进行初始化!!!
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
//这里可以自定义异常处理器
return AsyncConfigurer.super.getAsyncUncaughtExceptionHandler();
}
}
方式2: 定义配置类,直接注册ThreadPoolTaskExecutor
@EnableAsync
@Configuration
public class AsyncConfig {
@Bean
public AsyncTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setThreadNamePrefix("async-demo");
executor.setMaxPoolSize(10);
executor.setMaxPoolSize(100);
executor.setQueueCapacity(100);
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60 * 10);
return executor;
}
}
其中,关于线程池的设置有:
corePoolSize
: 核心线程数,当向线程池提交一个任务时池里的线程数小于核心线程数,那么它会创建一个线程来执行这个任务,一直直到池内的线程数等于核心线程数maxPoolSize
: 最大线程数,线程池中允许的最大线程数量。关于这两个数量的区别我会在下面解释queueCapacity
: 缓冲队列大小,用来保存阻塞的任务队列(注意这里的队列放的是任务而不是线程)keepAliveSeconds
: 允许线程存活时间(空闲状态下),单位为秒,默认60snamePrefix
: 线程名前缀RejectedExecutionHandler
:拒绝策略,当线程池达到最大线程数时,如何处理新任务。线程池为我们提供的策略有- AbortPolicy:默认策略。直接抛出RejectedExecutionExecption异常
- DiscardPolicy:直接丢弃掉被拒绝的任务,且不会抛出任何异常
- DiscardOldestPolicy:丢弃掉队列中的队头元素(也就是最早在队列里的任务),然后重新执行 提交该任务 的操作
- CallerRunsPolicy:由主线程自己来执行这个任务,该机制将减慢新任务的提交
- 或者可以自定义拒绝策略…
3. 异步任务类:
然后,我们需要定义自己的异步任务方法,这里创建了一个异步任务类,进行模拟:
@Component
@Slf4j
public class AsyncDemo {
@Async
public void async01() {
log.info("简单的异步调用...");
}
@Async
public void async02(String param) {
log.info("带参数的异步调用,参数:{}...", param);
}
@Async
public Future<String> async03(int i) {
log.info("带回调函数的异步调用...");
Future<String> future;
try {
Thread.sleep(1000);
future = new AsyncResult<String>("success" + i);
} catch (InterruptedException e) {
future = new AsyncResult<String>("error");
}
return future;
}
}
测试代码:
@SpringBootTest
class AsyncDemoApplicationTests {
@Autowired
private AsyncDemo asyncDemo;
@Test
void contextLoads() throws ExecutionException, InterruptedException {
System.out.println("主线程执行...");
asyncDemo.async01();
asyncDemo.async02("test");
Future<String> future = asyncDemo.async03(100);
System.out.println(future.get());
}
}
输出结果:
主线程执行...
2022-09-26 14:07:51.853 INFO 15814 --- [ async1] com.feng.async.demo01.AsyncDemo : 简单的异步调用...
2022-09-26 14:07:51.853 INFO 15814 --- [ async3] com.feng.async.demo01.AsyncDemo : 带回调函数的异步调用...
2022-09-26 14:07:51.853 INFO 15814 --- [ async2] com.feng.async.demo01.AsyncDemo : 带参数的异步调用,参数:test...
success100
注意事项:
- @Async修饰的方法必须为public方法
- **@Async注解修饰的方法一定需要在其他类中进行调用,即在该异步方法所在类的外部进行调用,如果在该类的内部进行调用,则异步方法将会失效,变成同步!!!**这是因为@Async注解的实现是基于Spring的AOP,而AOP的实现又是通过动态代理实现的。
- 异步方法的返回值只能为void或者Future