背景
日常开发中我们经常会遇到一些业务逻辑耗时较长、或者是和主体逻辑关联并不强、又或者是不需要立即得到执行结果。如:邮件通知、日志记录、数据同步。这时我们可以用到注解@Async。我们在使用该注解时,不加任何参数指定线程池,则会默认使用Spring自带的 SimpleAsyncTaskExecutor 线程池,当并发大的时候会不断的创建线程导致严重影响性能。所以日常开发规范都要求异步显性指定线程池。
简介
@Async是Spring的注解,可以加在类或方法上。通俗的来讲,如果加上了这个注解,那么该类或者该方法在使用时将会进行异步处理,也就是创建一个线程来实现这个类或者方法,实现多线程。
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Async {
String value() default "";
}
线程池的执行逻辑:
spring 已经实现的线程池
- SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,默认每次调用都会创建一个新的线程。
- SyncTaskExecutor:这个类没有实现异步调用,只是一个同步操作。只适用于不需要多线程的地方
- ConcurrentTaskExecutor:Executor的适配类,不推荐使用。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类。
- SimpleThreadPoolTaskExecutor:是Quartz的SimpleThreadPool的类。线程池同时被quartz和非quartz使用,才需要使用此类。
- ThreadPoolTaskExecutor :最常使用,推荐。其实质是对java.util.concurrent.ThreadPoolExecutor的包装。
使用方式:
第一种(不推荐):
使用的是Spring默认的线程池。
接入步骤:
1、需要在@SpringBootApplication启动类或者@configure注解类上 添加注解@EnableAsync启动多线程注解。
2、在需要异步执行的方法上添加@Async注解。
//启动类加上@EnableAsync注解
@EnableAsync
@SpringBootApplication
public class BizAdminApplication {
public static void main(String[] args) {
SpringApplication.run(BizAdminApplication.class, args);
}
}
//controller 方法接口
@GetMapping(value = "/testAsync", produces = MediaType.APPLICATION_JSON_VALUE)
public String testAsync() {
centerService.testAsync();
return "success";
}
//异步执行方法
@Async
public void testAsync() {
try {
System.out.println(Thread.currentThread());
Thread.sleep(2000);
System.out.println("异步发送邮件");
} catch (Exception e) {
}
}
执行后输出的结果
第二种(推荐):
使用自定义的线程池。
接入步骤:
1、创建一个配置类ThreadPoolTaskConfig,并在该配置类上加上注解@EnableAsync,表示启动多线程。
2、在需要异步执行的方法上添加@Async注解,并指定线程池名称
//添加一个配置类
@Configuration
@EnableAsync
public class ThreadPoolTaskConfig {
//核心线程数(默认线程数)
private static final int CORE_POOL_SIZE = 15;
//最大线程数
private static final int MAX_POOL_SIZE = 100;
//允许线程空闲时间(单位:默认为秒)
private static final int KEEP_ALIVE_TIME = 8;
//缓冲队列大小
private static final int QUEUE_CAPACITY = 150;
//线程池名前缀
private static final String THREAD_NAME_PREFIX = "Async-Service-";
// bean的名称,默认为首字母小写的方法名
@Bean("taskExecutor")
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(CORE_POOL_SIZE);
executor.setMaxPoolSize(MAX_POOL_SIZE);
executor.setQueueCapacity(QUEUE_CAPACITY);
executor.setKeepAliveSeconds(KEEP_ALIVE_TIME);
executor.setThreadNamePrefix(THREAD_NAME_PREFIX);
// 线程池对拒绝任务的处理策略
// CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 初始化
executor.initialize();
return executor;
}
}
//controller 方法接口
@GetMapping(value = "/testAsync", produces = MediaType.APPLICATION_JSON_VALUE)
public String testAsync() {
centerService.testAsync();
return "success";
}
//异步执行方法
@Async(value = "taskExecutor")
public void testAsync() {
try {
System.out.println(Thread.currentThread());
Thread.sleep(2000);
System.out.println("异步发送邮件");
} catch (Exception e) {
}
}
执行后输出的结果
注意事项
1、@Async会存在一些失效的情况
- 注解@Async的方法不是public方法;
- 注解@Async的返回值只能为void或Future;
- 注解@Async方法使用static修饰也会失效;
- 启动类没加@EnableAsync注解;
- 调用方和@Async不能在一个类中;
- 注解@Async所在类的使用需要自动注入,如果手动new会失效
- 在Async方法上标注@Transactional是没用的,但在Async方法调用的方法上标注@Transcational是有效的;
2、无返回值调用
对于返回值是void,异常会被AsyncUncaughtExceptionHandler处理掉,如果我们需要抛异常,则手动new一个异常抛出。
@Async(value = "taskExecutor")
public void testAsync(String msg) {
try {
System.out.println("异步发送邮件");
throw new IllegalArgumentException(msg);
} catch (Exception e) {
}
}
3、有返回值Future调用
对于返回值是Future,不会被AsyncUncaughtExceptionHandler处理,需要我们在方法中捕获异常并处理或者在调用方在调用Futrue.get时捕获异常进行处理
@Async(value = "taskExecutor")
public Future<String> testAsync(String msg) {
Future<String> future;
try {
Thread.sleep(2000);
System.out.println("异步发送邮件");
future = new AsyncResult<String>("success:" + i);
throw new IllegalArgumentException(msg);
} catch (InterruptedException e) {
future = new AsyncResult<String>("error");
} catch(IllegalArgumentException e){
future = new AsyncResult<String>("error-IllegalArgumentException");
}
return future;
}