1 引言
在java中异步线程很重要,比如在业务流处理时,需要通知硬件设备,发短信通知用户,或者需要上传一些图片资源到其他服务器这种耗时的操作,在主线程里处理会阻塞整理流程,而且我们也不需要等待处理结果之后再进行下一步操作,这时候就可以使用异步线程进行处理,这样主线程不会因为这些耗时的操作而阻塞,保证主线程的流程可以正常进行。
2 异步说明和原理
使用地方说明:
在方法上使用该@Async注解,申明该方法是一个异步任务;
在类上面使用该@Async注解,申明该类中的所有方法都是异步任务;
使用此注解的方法的类对象,必须是spring管理下的bean对象;
要想使用异步任务,需要在调用方法的类上配置@EnableAsync注解;
@Async的原理概括:
@Async的原理是通过 Spring AOP 动态代理 的方式来实现的。
在线程调用@Async注解标注的方法时,会调用代理,执行切入点处理器invoke方法,将方法的执行提交给线程池中的另外一个线程来处理,从而实现异步执行。
所以,相同类中的方法调用带@Async的方法是无法异步的,这种情况仍然是同步。
3 @Async的使用
3.1 调用方法的类上配置@EnableAsync注解
@Slf4j@Service@EnableAsyncpublicclassDataAllServiceImplimplementsDataAllService{@AutowiredprivateDataSyncService dataSyncService;/**
* 同步所有数据
*/@OverridepublicvoidsyncAll(){
dataSyncService.syncPerson();}}
3.2 方法或者类上加@Async注解
@AsyncpublicclassDataSyncServiceImplimplementsDataSyncService{@OverridepublicvoidsyncPerson(){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:6core-size:3keep-alive: 3s
queue-capacity:1000thread-name-prefix: name
4.3 自定义线程池
编写配置类
@Configuration@DatapublicclassExecutorConfig{//核心线程privateint corePoolSize;//最大线程privateint maxPoolSize;//队列容量privateint queueCapacity;//保持时间privateint keepAliveSeconds;//名称前缀privateString preFix;@Bean("MyExecutor")publicExecutormyExecutor(){ThreadPoolTaskExecutor executor =newThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setKeepAliveSeconds(keepAliveSeconds);
executor.setThreadNamePrefix(preFix);
executor.setRejectedExecutionHandler(newThreadPoolExecutor.AbortPolicy());
executor.initialize();return executor;}}
使用自定义线程池
@ComponentpublicclassMyAsyncTask{@Async("MyExecutor")//使用自定义的线程池(执行器)publicvoidasyncCpsItemImportTask(Long platformId,String jsonList){//...具体业务逻辑}}
5 异步中的事务和返回
5.1异步中的事务
在@Async标注的方法,同时也使用@Transactional进行标注;在其调用数据库操作之时,将无法产生事务管理的控制,原因就在于其是基于异步处理的操作。
那该如何给这些操作添加事务管理呢?
可以将需要事务管理操作的方法放置到异步方法内部,在内部被调用的方法上添加@Transactional
5.2 异步返回
异步的业务逻辑处理场景 有两种:一个是不需要返回结果,另一种是需要接收返回结果。不需要返回结果的比较简单,就不多说了。
需要接收返回结果的示例如下
@Async("MyExecutor")publicFuture<Map<Long,List>>queryMap(List ids){List<> result = businessService.queryMap(ids);..............Map<Long,List> resultMap =Maps.newHashMap();...returnnewAsyncResult<>(resultMap);}
6 失效情况分析
6.1 未使用@EnableAsync注解
在Spring中要开启@Async注解异步的功能,需要在项目的启动类,或者配置类上,或者调用方法的类上使用@EnableAsync注解。
6.2 内部方法调用
我们在日常开发中,经常需要在一个方法中调用另外一个方法。例如:
@Slf4j@ServicepublicclassUserService{publicvoidtest(){async("test");}@Asyncpublicvoidasync(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生成代理类,因此需要加上以上注解。
版权归原作者 陌上花开zz 所有, 如有侵权,请联系我们删除。