前言
最近在做一个用户反馈的功能,就是当用户反馈意见或建议后服务端将意见保存然后发邮件给相关模块的开发者。考虑发邮件耗时的情况所以我想用异步的方法去执行,于是就开始研究Spring的@Async了。但网上的许多教程都是相互抄袭且直接复制代码的,根本没有去自己实践或者更改代码,所以在这两天我遇到了许多问题,使得@Async无效,也一直没有找到很好的文章去详细的说明@Async的正确及错误的使用方法及需要注意的地方,这里Sring是以配置文件的形式来开启@Async,而SpringBoot则是以注解的方式开启。教程
Spring配置文件
配置文件的话有两种方式一种是精简式:
直接在applicationContext.xml中加入开启异步并添加task的命名空间<task:executor id="WhifExecutor" pool-size="10"/> <task:annotation-driven executor="WhifExecutor" />
xmlns:task="http://www.springframework.org/schema/task"http://www.springframework.org/schema/taskhttp://www.springframework.org/schema/task/spring-task.xsd
这样好处是简单快速,缺点是线程池配置简单,线程创建不能定时关闭。
所以我推荐下面这种,定义一个线程池,然后引入。其实两种方法原理是一样只不过下面这种只是线程池配置更加详细,线程在空闲后会根据存活时间配置进行关闭。
在applicationContext.xml同目录下创建文件threadPool.xml内容如下,然后在applicationContext.xml中引入threadPool.xml:<import resource="threadPool.xml" /><?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd"> <!-- 开启异步,并引入线程池 --> <task:annotation-driven executor="threadPool" /> <!-- 定义线程池 --> <bean id="threadPool" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> <!-- 核心线程数,默认为1 --> <property name="corePoolSize" value="5" /> <!-- 最大线程数,默认为Integer.MAX_VALUE --> <property name="maxPoolSize" value="20" /> <!-- 队列最大长度,一般需要设置值>=notifyScheduledMainExecutor.maxNum;默认为Integer.MAX_VALUE --> <property name="queueCapacity" value="500" /> <!-- 线程池维护线程所允许的空闲时间,默认为60s --> <property name="keepAliveSeconds" value="30" /> <!-- 完成任务自动关闭 , 默认为false--> <property name="waitForTasksToCompleteOnShutdown" value="true" /> <!-- 核心线程超时退出,默认为false --> <property name="allowCoreThreadTimeOut" value="true" /> <!-- 线程池对拒绝任务(无线程可用)的处理策略,目前只支持AbortPolicy、CallerRunsPolicy;默认为后者 --> <property name="rejectedExecutionHandler"> <!-- AbortPolicy:直接抛出java.util.concurrent.RejectedExecutionException异常 --> <!-- CallerRunsPolicy:主线程直接执行该任务,执行完之后尝试添加下一个任务到线程池中,可以有效降低向线程池内添加任务的速度 --> <!-- DiscardOldestPolicy:抛弃旧的任务、暂不支持;会导致被丢弃的任务无法再次被执行 --> <!-- DiscardPolicy:抛弃当前任务、暂不支持;会导致被丢弃的任务无法再次被执行 --> <bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" /> </property> </bean> </beans>
SpringBoot则是添加@EnableAsync注解
@EnableAsync @SpringBootApplication @ServletComponentScan @MapperScan("com.cmc.schedule.model.mapper") //配置扫描mapper接口的地址 public class NLPApplication extends SpringBootServletInitializer { //不使用springboot内嵌tomcat启动方式 @Override protected SpringApplicationBuilder configure( SpringApplicationBuilder application) { return application.sources(NLPApplication.class); } public static void main(String[] args) { SpringApplication.run(NLPApplication.class, args); } //默认使用fastjson解析 @Bean public HttpMessageConverters fastJsonHttpMessageConverters() { FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter(); FastJsonConfig fastJsonConfig = new FastJsonConfig(); fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat); fastConverter.setFastJsonConfig(fastJsonConfig); HttpMessageConverter<?> converter = fastConverter; return new HttpMessageConverters(converter); } }
这一步Spring和SpringBoot都是一样的,创建一个异步方法的类
package com.cmc.tst;import org.springframework.scheduling.annotation.Async;import org.springframework.stereotype.Component;/** * @Component 注解必须要有,否则无法将此bean注入 * 当然也可以使用其他的注解,只要可以装配就行 * * @author chenmc * @date 2017年9月4日 下午3:38:29 */@Componentpublic class MyAsync { /** * @Async 表明这是一个异步方法,也就是说当调用这个方法时, * spring会创建一条线程来执行这个方法。 * 注意:不能使用static来修饰此方法,否则@Async无效 * * @author chenmc * @date 2017年9月4日 下午3:34:24 */ @Async public void asyncMethod(){ System.out.println(Thread.currentThread().getName()); } }
测试调用异步方法,我这里使用的是JUnit4测试
@Autowired MyAsync async; @Test public void test() { System.out.println(Thread.currentThread().getName() + "start"); //MyAsync async = new MyAsync(); //自己new出来的对象@Async将无效,必须要spring注入的 async.asyncMethod(); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "end"); }
总结
这里总结一下@Async注解无效的可能点
一、异步方法使用static修饰
二、异步类没有使用@Component注解(或其他注解)导致spring无法扫描到异步类
三、测试异步方法不能与异步方法在同一个类中
四、测试类中需要使用@Autowired或@Resource等注解自动注入,不能自己手动new对象
五、如果使用SpringBoot框架必须在启动类中增加@EnableAsync注解
结语
spring的@Async真的极大的方便了java的异步(多线程)开发,心里默念三遍:感谢spring!感谢spring!感谢spring!