在java中异步线程很重要,比如在业务流处理时,需要通知硬件设备,发短信通知用户,或者需要上传一些图片资源到其他服务器这种耗时的操作,在主线程里处理会阻塞整理流程,而且我们也不需要等待处理结果之后再进行下一步操作,这时候就可以使用异步线程进行处理,这样主线程不会因为这些耗时的操作而阻塞,保证主线程的流程可以正常进行。
最近在项目中使用了很多线程的操作,在这做个记录
使用地方说明:
@Async的原理概括:
@Async的原理是通过 Spring AOP 动态代理 的方式来实现的。
Spring 容器启动初始化bean时,判断类中是否使用了@Async注解,如果使用了则为其创建切入点和切入点处理器,根据切入点创建代理,在线程调用@Async注解标注的方法时,会调用代理,执行切入点处理器invoke方法,将方法的执行提交给线程池中的另外一个线程来处理,从而实现了异步执行。
所以,需要注意的一个错误用法是,如果a方法调用它同类中的标注@Async的b方法,是不会异步执行的,因为从a方法进入调用的都是该类对象本身,不会进入代理类。
因此,相同类中的方法调用带@Async的方法是无法异步的,这种情况仍然是同步。
在Spring中启用@Async:
以Spring boot为例,启动类中增加@EnableAsync:
@EnableAsync @SpringBootApplication public class ManageApplication { //... }
@Component public class MyAsyncTask { @Async public void asyncCpsItemImportTask(Long platformId, String jsonList){ //...具体业务逻辑 } }
上面的配置会启用默认的线程池/执行器,异步执行指定的方法。
Spring默认的线程池的默认配置:
默认核心线程数:8,
最大线程数:Integet.MAX_VALUE,
队列使用LinkedBlockingQueue,
容量是:Integet.MAX_VALUE,
空闲线程保留时间:60s,
线程池拒绝策略:AbortPolicy
缺点:从最大线程数的配置上,相信看到问题:并发情况下,会无限创建线程
默认线程池的上述缺陷如何解决:答案是,自定义配置参数就可以了
spring: task: execution: pool: max-size: 6 core-size: 3 keep-alive: 3s queue-capacity: 1000 thread-name-prefix: name
在业务场景中,有时需要使用自己定义的执行器来跑异步的业务逻辑,那该怎么办呢?答案是,自定义线程池。
@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; } }
在方法上的@Async注解处指定线程池名字:
@Component public class MyAsyncTask { @Async("MyExecutor") //使用自定义的线程池(执行器) public void asyncCpsItemImportTask(Long platformId, String jsonList){ //...具体业务逻辑 } }
Spring用TaskExecutor和TaskScheduler接口提供了异步执行和调度任务的抽象。
Spring的TaskExecutor和java.util.concurrent.Executor接口时一样的,这个接口只有一个方法execute(Runnable task)。
Spring已经内置了许多TaskExecutor的实现,没有必要自己去实现:
点击了解Spring线程池ThreadPoolTaskExecutor讲解
在@Async标注的方法,同时也使用@Transactional进行标注;在其调用数据库操作之时,将无法产生事务管理的控制,原因就在于其是基于异步处理的操作。
那该如何给这些操作添加事务管理呢?
可以将需要事务管理操作的方法放置到异步方法内部,在内部被调用的方法上添加@Transactional
示例:
异步的业务逻辑处理场景 有两种:一个是不需要返回结果,另一种是需要接收返回结果。不需要返回结果的比较简单,就不多说了。
需要接收返回结果的示例如下:
@Async("MyExecutor") public Future
调用异步方法的示例:
public MapasyncProcess(List bindDevices,List bindStaffs, String dccId) { Map finalMap =null; // 返回值: Future
使用了异步但是执行异步的方法,原因是在方法上加了@Async注解,之所以加这个注解是因为报错:
There was an unexpected error (type=Internal Server Error, status=500).
Async support must be enabled on a servlet and for all filters involved in async request processing. This is done in Java code using the Servlet API or by adding true to servlet and filter declarations in web.xml
异步测试时一直报这个错误,提示我在web.xml开启异步支持,但是我是SpringBoot项目,于是开始网上查找
错误:加@Async注解,会更加异步,不能获取异步结果
正确:根本原因是容器注册问题,在springboot启动类的注解@SpringBootApplication旁边添加了@ServletComponentScan,才导致上面的报错和不能回调,有三种解决方法:
@Bean public ServletRegistrationBean dispatcherServlet() { ServletRegistrationBean registration = new ServletRegistrationBean( new DispatcherServlet(), "/"); registration.setAsyncSupported(true); return registration; } @Bean DispatcherServlet dispatcherServlet(){ return new DispatcherServlet(); }
在过滤器那里添加asyncSupported = true的支持
@WebFilter(urlPatterns="/*",asyncSupported = true)
需要在 web.xml 文件中的 servlet 定义中添加:"true"
Archetype Created Web Application contextConfigLocation classpath:spring-mybatis.xml spring.profiles.active dev spring.profiles.default dev encodingFilter org.springframework.web.filter.CharacterEncodingFilter true encoding UTF-8 forceEncoding true encodingFilter /* org.springframework.web.context.ContextLoaderListener SpringMVC org.springframework.web.servlet.DispatcherServlet contextConfigLocation classpath:spring-mvc.xml 1 true SpringMVC /