前言
在实际项目开发中很多业务场景需要使用异步去完成,比如消息通知,日志记录,等非常常用的都可以通过异步去执行,提高效率,那么在Spring
框架中应该如何去使用异步呢
使用步骤
完成异步操作一般有两种,
消息队列MQ
,和线程池处理ThreadPoolExecutor
而在Spring4
中提供的对ThreadPoolExecutor封装的线程池ThreadPoolTaskExecutor
,直接使用注解启用@Async
,这个注解让我们在使用Spring完成异步操作变得非常方便
配置线程池类参数配置
自定义常量类
1 | java复制代码public class ConstantFiledUtil { |
配置线程池
1 | java复制代码@Configuration(proxyBeanMethods = false) |
注意这里需要通过
@EnableAsync
开启异步否则无效
自定义线程任务
1 | java复制代码public interface ILogService extends IService<Log> { |
在需要异步执行的接口
,或者方法
上加上@Async
注解此方法就是异步方法,在主程序中调用的化,就是异步方式单独线程执行
此注解表明saveLog
方法进入的线程池是KmallThreadPool方法创建的。
我们也可以单独指定方法名称@Async("saveLogs")
这样在进行日记记录的时候就是单独线程执行每次请求
都快速响应了,而耗时的操作都留给线程池中的线程去异步执行
总结
Spring中用@Async注解标记的方法,称为异步方法。在spring boot应用中使用@Async很简单:
- 调用异步方法类上或者启动类加上注解@EnableAsync
- 在需要被异步调用的方法外加上@Async
- 所使用的@Async注解方法的类对象应该是Spring容器管理的bean对象;
注意同一个类里面调用异步方法不生效:原因默认类内的方法调用不会被
aop
拦截,即调用方和被调用方是在同一个类中,是无法产生切面的,该对象没有被Spring容器管理。即@Async
方法不生效
解决办法:
如果要使同一个类中的方法之间调用也被拦截,需要使用spring容器中的实例对象,而不是使用默认的this,因为通过bean实例的调用才会被spring的aop拦截
本例使用方法:AsyncService asyncService = context.getBean(AsyncService.class);
然后使用这个引用调用本地的方法即可达到被拦截的目的备注:这种方法只能拦截protected,default,public方法,private方法无法拦截。这个是spring aop的一个机制。
原理刨析
异步方法返回类型只能有两种:
- 当返回类型为void的时候,方法调用过程产生的异常不会抛到调用者层面,可以通过注
AsyncUncaughtExceptionHandler
来捕获此类异常 - 当返回类型为Future的时候,方法调用过程产生的异常会抛到调用者层面
注意如果不自定义异步方法的线程池默认使用
SimpleAsyncTaskExecutor
。SimpleAsyncTaskExecutor
:不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。并发大的时候会产生严重的性能问题
。
Spring异步线程池接口 TaskExecutor
看源码可知
1 | java复制代码@FunctionalInterface |
它的实先类有很多如下:
SimpleAsyncTaskExecutor:
不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。SyncTaskExecutor:
这个类没有实现异步调用,只是一个同步操作。只适用于不需要多线程的地方- ConcurrentTaskExecutor:Executor的适配类,不推荐使用。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类
SimpleThreadPoolTaskExecutor:
是Quartz的SimpleThreadPool的类。线程池同时被quartz和非quartz使用,才需要使用此类ThreadPoolTaskExecutor :
最常使用,推荐。 其实质是对java.util.concurrent.ThreadPoolExecutor的包装,
————————————————
文章参考
本文转载自: 掘金