Spring的异步详解(@Async)
1 引言
在java中异步线程很重要,比如在业务流处理时,需要通知硬件设备,发短信通知用户,或者需要上传一些图片资源到其他服务器这种耗时的操作,在主线程里处理会阻塞整理流程,而且我们也不需要等待处理结果之后再进行下一步操作,这时候就可以使用异步线程进行处理,这样主线程不会因为这些耗时的操作而阻塞,保证主线程的流程可以正常进行。
2 异步说明和原理
使用地方说明:
在方法上使用该@Async注解,申明该方法是一个异步任务;
在类上面使用该@Async注解,申明该类中的所有方法都是异步任务;
使用此注解的方法的类对象,必须是spring管理下的bean对象;
要想使用异步任务,需要在调用方法的类上配置@EnableAsync注解;
@Async的原理概括:
@Async的原理是通过 Spring AOP 动态代理 的方式来实现的。
在线程调用@Async注解标注的方法时,会调用代理,执行切入点处理器invoke方法,将方法的执行提交给线程池中的另外一个线程来处理,从而实现异步执行。
所以,相同类中的方法调用带@Async的方法是无法异步的,这种情况仍然是同步。
3 @Async的使用
3.1 调用方法的类上配置@EnableAsync注解
@Slf4j
@Service
@EnableAsync
public class DataAllServiceImpl implements DataAllService {
@Autowired
private DataSyncService dataSyncService;
/**
* 同步所有数据
*/
@Override
public void syncAll() {
dataSyncService.syncPerson();
}
}
3.2 方法或者类上加@Async注解
@Async
public class DataSyncServiceImpl implements DataSyncService {
@Override
public void syncPerson(){
XxlJobHelper.log("开始采集Person表");
}
4异步线程池
4.1 Spring默认的线程池的默认配置
1.默认核心线程数:8,
2. 最大线程数:Integet.MAX_VALUE,
3. 队列使用LinkedBlockingQueue,
4. 容量是:Integet.MAX_VALUE,
5. 空闲线程保留时间:60s,
6. 线程池拒绝策略:AbortPolicy
缺点:从最大线程数的配置上,相信看到问题:并发情况下,会无限创建线程
默认线程池的上述缺陷如何解决:答案是,自定义配置参数就可以了
4.2在配置文件中配置
spring:
task:
execution:
pool:
max-size: 6
core-size: 3
keep-alive: 3s
queue-capacity: 1000
thread-name-prefix: name
4.3 自定义线程池
编写配置类
@Configuration
@Data
public class ExecutorConfig{
//核心线程
private int corePoolSize;
//最大线程
private int maxPoolSize;
//队列容量
private int queueCapacity;
//保持时间
private int keepAliveSeconds;
//名称前缀
private String preFix;
@Bean("MyExecutor")
public Executor myExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setKeepAliveSeconds(keepAliveSeconds);
executor.setThreadNamePrefix(preFix);
executor.setRejectedExecutionHandler( new ThreadPoolExecutor.AbortPolicy());
executor.initialize();
return executor;
}
}
使用自定义线程池
@Component
public class MyAsyncTask {
@Async("MyExecutor") //使用自定义的线程池(执行器)
public void asyncCpsItemImportTask(Long platformId, String jsonList){
//...具体业务逻辑
}
}
5 异步中的事务和返回
5.1异步中的事务
在@Async标注的方法,同时也使用@Transactional进行标注;在其调用数据库操作之时,将无法产生事务管理的控制,原因就在于其是基于异步处理的操作。
那该如何给这些操作添加事务管理呢?
可以将需要事务管理操作的方法放置到异步方法内部,在内部被调用的方法上添加@Transactional
5.2 异步返回
异步的业务逻辑处理场景 有两种:一个是不需要返回结果,另一种是需要接收返回结果。不需要返回结果的比较简单,就不多说了。
需要接收返回结果的示例如下
@Async("MyExecutor")
public Future<Map<Long, List>> queryMap(List ids) {
List<> result = businessService.queryMap(ids);
..............
Map<Long, List> resultMap = Maps.newHashMap();
...
return new AsyncResult<>(resultMap);
}
6 失效情况分析
6.1 未使用@EnableAsync注解
在Spring中要开启@Async注解异步的功能,需要在项目的启动类,或者配置类上,或者调用方法的类上使用@EnableAsync注解。
6.2 内部方法调用
我们在日常开发中,经常需要在一个方法中调用另外一个方法。例如:
@Slf4j
@Service
public class UserService {
public void test() {
async("test");
}
@Async
public void async(String value) {
log.info("async:{}", value);
}
}
6.3 方法非public
因为private修饰的方法,只能在UserService类的对象中使用。
而@Async注解的异步功能,需要使用Spring的AOP生成UserService类的代理对象,该代理对象没法访问UserService类的private方法,因此会出现@Async注解失效的问题。
6.4 方法返回值错误
想要使用@Async注解的异步功能,相关方法的返回值必须是void或者Future。
6.5 方法用static修饰了
使用@Async注解声明的方法,必须是能被重写的,很显然static修饰的方法,是类的静态方法,是不允许被重写的。
因此这种情况下,@Async注解的异步功能会失效。
6.6 方法用final修饰
在Java种final关键字,是一个非常特别的存在。
用final修饰的类,没法被继承。
用final修饰的方法,没法被重写。
用final修饰的变量,没法被修改。
如果final使用不当,也会导致@Async注解的异步功能失效
6.7 业务类没加@Service@controller@component等注解
业务类需要交给Spring管理,通过AOP生成代理类,因此需要加上以上注解。
参考:https://jingzh.blog.csdn.net/article/details/129769200?fromshare=blogdetail&sharetype=blogdetail&sharerId=129769200&sharerefer=PC&sharesource=weixin_43228381&sharefrom=from_link