在做项目过程中,一些耗时长的任务可能需要在后台线程池中运行;典型的如发送邮件等,由于需要调用外部的接口来进行实际的发送操作,如果客户端在提交发送请求后一直等待服务器端发送成功后再返回,就会长时间的占用服务器的一个连接;当这类请求过多时,服务器连接数会不够用,新的连接请求可能无法得到满足,从而导致客户端连接失败。因此这类服务一般需要使用到后台线程池来处理。

在这种情况下,我们可以直接使用concurrent包中的线程池来处理,也可以使用其它的方案如Quartz等组件中的线程池来解决;为适配这些不同的方案,Spring引入了TaskExecutor接口作为顶层接口,并提供了几种不同的实现来满足不同的场景。

常见实现

Spring包含了以下TaskExecutor的实现:

  • ThreadPoolTaskExecutor

它是最经常使用的一个,提供了一些Bean属性用于配置java.util.concurrent.ThreadPoolExecutor并且将其包装到TaskExecutor对象中。如果需要适配java.util.concurrent.Executor,请使用ConcurrentTaskExecutor。

  • SimpleAsyncTaskExecutor

线程不会重用,每次调用时都会重新启动一个新的线程;但它有一个最大同时执行的线程数的限制;

  • SyncTaskExecutor

同步的执行任务,任务的执行是在主线程中,不会启动新的线程来执行提交的任务。主要使用在没有必要使用多线程的情况,如较为简单的测试用例。

  • ConcurrentTaskExecutor

它用于适配java.util.concurrent.Executor, 一般情况下请使用ThreadPoolTaskExecutor,如果hreadPoolTaskExecutor不够灵活时可以考虑采用ConcurrentTaskExecutor。

  • SimpleThreadPoolTaskExecutor

它是Quartz中SimpleThreadPool的一个实现,用于监听Spring生命周期回调事件。它主要使用在需要一个线程池来被Quartz和非Quartz中的对象同时共享使用的情况。

  • WorkManagerTaskExecutor

它实现了CommonJ中的WorkManager接口,是在Spring中使用CommonJ的WorkManager时的核心类。

使用示例

注册TaskExecutor

此处通过spring Boot工程进行演示;在配置类中注册Bean:

  1. @Configuration
  2. public class MainConfiguration {
  3. @Bean
  4. public TaskExecutor getTaskExecutor() {
  5. ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
  6. taskExecutor.setMaxPoolSize(10);
  7. taskExecutor.setCorePoolSize(5);
  8. taskExecutor.setQueueCapacity(20);
  9. return taskExecutor;
  10. }
  11. }

此时TaskExecutor对象已经被注入Spring中,接下来就可以通过Autowired来使用。

使用TaskExecutor

使用示例较为简单,直接在一个Controller中来使用TaskExecutor提交一个测试的任务;通过打印日志来分析其执行过程;

示例代码如下:

  1. @RestController
  2. @RequestMapping("/test")
  3. public class TestController {
  4. private static final Logger logger = LoggerFactory.getLogger(TestController.class);
  5. @Autowired
  6. private TaskExecutor taskExecutor;
  7. @RequestMapping("/testTaskExecutor")
  8. public String testTaskExecutor() {
  9. logger.info("TestTaskExecutor function begin to execute!");
  10. taskExecutor.execute(() -> {
  11. logger.info("Real thread begin to execute!");
  12. try {
  13. Thread.sleep(5000);
  14. } catch (InterruptedException e) {
  15. logger.error("Real thread was interrupted!", e);
  16. return;
  17. }
  18. logger.info("Real thread has been executed!");
  19. });
  20. logger.info("TestTaskExecutor function has been executed!");
  21. return "Succeed!";
  22. }
  23. }

通过浏览器打开网址:http://localhost/test/testTaskExecutor,后台打印日志如下所示:

  1. 2018-03-12 15:31:17.430 INFO 9916 --- [p-nio-80-exec-9] c.l.t.b.e.controllers.TestController : TestTaskExecutor begins to execute!
  2. 2018-03-12 15:31:17.431 INFO 9916 --- [p-nio-80-exec-9] c.l.t.b.e.controllers.TestController : TestTaskExecutor execution completed!
  3. 2018-03-12 15:31:17.431 INFO 9916 --- [tTaskExecutor-1] c.l.t.b.e.controllers.TestController : Real thread begins to execute!
  4. 2018-03-12 15:31:22.438 INFO 9916 --- [tTaskExecutor-1] c.l.t.b.e.controllers.TestController : Real thread has been executed!

可以明显地看到TaskExecutor中提交的任务,与主线程是在两个线程中。

使用Async

在2中直接使用了taskExecutor来提交任务,这个时候还需要实现一个Runable的接口来实现具体的任务逻辑。实际上这个过程通过Async注解来进行简化。

Async注解用于表示方法需要异步调用,此时Spring会使用后台的线程池来异步的执行它所注解的方法;一般情况下这个方法的返回类型需要是void的;但也可以是Future类型的;当使用Future时即可对提交的任务执行情况进行判别。

Async注解也可用于类上,当用于类上时,相当于给所有的方法都默认加上了Async注解。

现在来实现一个AsyncService类,用于演示Async的使用:

AsyncService

其实现如下:

  1. @Service
  2. public class AsyncService {
  3. private static final Logger logger = LoggerFactory.getLogger(AsyncService.class);
  4. @Async
  5. public void testNoRespNoParamAsync() {
  6. logger.info("AsyncService begins to execute!");
  7. try {
  8. Thread.sleep(5000);
  9. } catch (InterruptedException e) {
  10. logger.error("AsyncService was interrupted!", e);
  11. return;
  12. }
  13. logger.info("AsyncService execution completed!");
  14. }
  15. }

TestController

在Controller中调用AsyncService,实现如下:

  1. @Autowired
  2. private AsyncService asyncService;
  3. @RequestMapping("/testAsync")
  4. public String testAsync() {
  5. logger.info("TestAsync begins to execute!");
  6. long startTime = System.currentTimeMillis();
  7. asyncService.testNoRespNoParamAsync();
  8. logger.info("TestAsync execution completed, use time: {}!", (System.currentTimeMillis() - startTime) / 1000);
  9. return "End!";
  10. }

添加EnableAsync

在spring Boot中使用Async,EnableAsync注解必须要与@SpringBootApplication一起使用,否则Async将不会生效,其结果就是执行结果并不会被异步执行,因此一定要记得在SpringBoot的启动类上添加该注解:

  1. @EnableAsync
  2. @SpringBootApplication
  3. public class EShopApplication {
  4. public static void main(String[] args) {
  5. SpringApplication.run(EShopApplication.class, args);
  6. }
  7. }

此时,再通过链接: http://localhost/test/testAsync进行访问,后台打印日志如下:

  1. 2018-03-12 16:03:13.974 INFO 9916 --- [-nio-80-exec-10] c.l.t.b.e.controllers.TestController : TestAsync begins to execute!
  2. 2018-03-12 16:03:13.979 INFO 9916 --- [-nio-80-exec-10] c.l.t.b.e.controllers.TestController : TestAsync execution completed, use time: 0!
  3. 2018-03-12 16:03:14.030 INFO 9916 --- [tTaskExecutor-1] c.l.t.b.eshop.service.AsyncService : AsyncService begins to execute!
  4. 2018-03-12 16:03:19.040 INFO 9916 --- [tTaskExecutor-1] c.l.t.b.eshop.service.AsyncService : AsyncService execution completed!

可以看到主线程是-nio-80-exec-10,TestController在03:13就执行完毕;而AsyncService中的方法在线程tTaskExecutor-1中执行,执行完成时间是03:19。