在实际的应用开发中,异步任务能够显著提高系统的响应性能和并发处理能力。Spring 框架为异步任务的实现提供了强大且便捷的支持。
异步任务的使用与注意事项
一、核心注解与配置
@Async需要和@EnableAsync一起使用才有效果
@Async注解
@Async 注解用于标记需要异步执行的方法。当方法被标记为 @Async 后,调用者在调用时会立即返回,方法的实际执行会被提交到 Spring task execute的任务执行机制中,由指定的线程池中的线程来负责执行。
@EnableAsync注解
@EnableAsync 注解需要标注在配置类上,用于启用异步方法执行的支持。只有当两者配合使用时,才能实现异步任务的功能。
二、异步任务的返回值
异步任务可以有返回值,也可以没有返回值。
- 无返回值的异步任务,专注于执行特定的业务逻辑,不向调用者返回结果。
- 有返回值的异步任务,通过返回 Future 对象,调用方能够在后续的合适时机获取异步执行的结果,或者基于这个 Future 对象进行其他相关操作,比如判断任务是否完成、获取结果等。
三、自定义线程池和异常处理器**
异步任务使用的是Spring定义的线程池SimpleAsyncTask,其实它不是真正的线程池,这个类不会重用线程,默认每次调用都会创建一个新的线程,效率是比较低的,而且不能很好的匹配业务特点。所以我们通常在使用异步任务的时候都会根据业务的特点去自定义线程池。
示例代码
(一)配置类中自定义线程池和异常处理器
package com.imooc.engineering.guide.async;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.lang.reflect.Method;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@Slf4j
@EnableAsync
@Configuration
public class AsyncConfiguration implements AsyncConfigurer {
/**
* 自定义异步任务执行器
* 在异步任务执行时使用这个自定义配置的线程池
* @return
*/
@Override
public Executor getAsyncExecutor() {
int cpuCount = Runtime.getRuntime().availableProcessors(); // 获取当前系统的CPU核心数
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(cpuCount * 2); // 核心线程池大小为CPU核心数的2倍
taskExecutor.setMaxPoolSize(cpuCount * 4); // 最大线程池大小为CPU核心数的4倍
taskExecutor.setQueueCapacity(50000); // 队列容量为50000
taskExecutor.setKeepAliveSeconds(60); // 线程存活时间为60秒
taskExecutor.setThreadNamePrefix("async-test:");
//拒绝策略
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy()); // 当任务提交时如果线程池已满且队列已满,则直接丢弃新提交的任务。
taskExecutor.setWaitForTasksToCompleteOnShutdown(true); // 在关闭时等待任务完成
taskExecutor.setAwaitTerminationSeconds(60); // 等待任务完成的超时时间为60秒
taskExecutor.initialize(); // 初始化线程池
return taskExecutor;
}
/**
* 处理异步任务中的未捕获异常
* @return
*/
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new AsyncUncaughtExceptionHandler(){
/**
* 当异步任务发生未捕获的异常时执行
* @param ex
* @param method
* @param params
*/
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
log.error("async error:");
}
};
}
}
(二)定义异步任务的接口与实现
接口
package com.imooc.engineering.guide.async;
import java.util.concurrent.Future;
/**
* 异步任务服务接口定义
*/
public interface IAsyncService {
void asyncMethodNoReturn() throws InterruptedException;
Future<String> asyncMethodHasReturn() throws InterruptedException;
}
实现类
package com.imooc.engineering.guide.async;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;
import java.util.concurrent.Future;
@Slf4j
@Service
public class AsyncServiceImpl implements IAsyncService{
/**
* 异步任务没有返回值
* @return
* @throws InterruptedException
*/
@Async
@Override
public void asyncMethodNoReturn() throws InterruptedException {
// 业务逻辑
}
/**
* 异步任务有返回值
* @return
* @throws InterruptedException
*/
@Async
@Override
public Future<String> asyncMethodHasReturn() throws InterruptedException {
//业务逻辑
return new AsyncResult<>("haha");
}
}
异步方法可以有返回值也可以没有返回值,asyncMethodNoReturn()定义了一个没有返回值的方法。
asyncMethodHasReturn()定义了一个有返回值的方法,返回Future 对象,调用方可以通过这个 Future 对象来获取异步执行的结果或者进行其他相关的操作,比如判断任务是否完成、获取结果等。
@Async 的原理及失效场景
原理
@Async 的原理基于 Spring AOP 的动态代理机制实现。在 Spring 容器启动并初始化 Bean 时,会检查类中是否标注了 @Async 注解。若有标注,将为其创建切入点和切入点处理器,并依据切入点生成代理。当线程调用被 @Async 注解标注的方法时,会触发代理,执行切入点处理器的 invoke 方法,将方法的执行交付给线程池中的另一个线程处理,以此达成异步执行的效果。
失效场景
一个方法调用了本类中被 @Async注解的另一个方法时,没有经过Spring AOP的代理类,此时不会异步执行。所以,只有在外部调用带有 @Async 注解方法的时候才会生效,在同一类中的方法调用带有 @Async 注解的方法无法实现异步,这种情形仍然是同步执行。