Spring基础学习-任务执行(TaskExecutor及Async)

在做项目过程中,一些耗时长的任务可能需要在后台线程池中运行;典型的如发送邮件等,由于需要调用外部的接口来进行实际的发送操作,如果客户端在提交发送请求后一直等待服务器端发送成功后再返回,就会长时间的占用服务器的一个连接;当这类请求过多时,服务器连接数会不够用,新的连接请求可能无法得到满足,从而导致客户端连接失败。因此这类服务一般需要使用到后台线程池来处理。
在这种情况下,我们可以直接使用concurrent包中的线程池来处理,也可以使用其它的方案如Quartz等组件中的线程池来解决;为适配这些不同的方案,Spring引入了TaskExecutor接口作为顶层接口,并提供了几种不同的实现来满足不同的场景。

本文目录:

  • 1 常见实现
  • 2 使用示例
    • 2.1 注册TaskExecutor
    • 2.2 使用TaskExecutor
    • 2.3 使用Async
      • 2.3.1 AsyncService
      • 2.3.2 TestController
      • 2.3.4 添加EnableAsync
  • 3. 参考资料

1 常见实现

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时的核心类。

2 使用示例

2.1 注册TaskExecutor

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

@Configuration
public class MainConfiguration {
    @Bean
    public TaskExecutor getTaskExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setMaxPoolSize(10);
        taskExecutor.setCorePoolSize(5);
        taskExecutor.setQueueCapacity(20);

        return taskExecutor;
    }
}

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

2.2 使用TaskExecutor

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

@RestController
@RequestMapping("/test")
public class TestController {
    private static final Logger logger = LoggerFactory.getLogger(TestController.class);
    @Autowired
    private TaskExecutor taskExecutor;

    @RequestMapping("/testTaskExecutor")
    public String testTaskExecutor() {
        logger.info("TestTaskExecutor function begin to execute!");

        taskExecutor.execute(() -> {
            logger.info("Real thread begin to execute!");

            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                logger.error("Real thread was interrupted!", e);
                return;
            }

            logger.info("Real thread has been executed!");
        });

        logger.info("TestTaskExecutor function has been executed!");

        return "Succeed!";
    }
}

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

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!
2018-03-12 15:31:17.431  INFO 9916 --- [p-nio-80-exec-9] c.l.t.b.e.controllers.TestController     : TestTaskExecutor execution completed!
2018-03-12 15:31:17.431  INFO 9916 --- [tTaskExecutor-1] c.l.t.b.e.controllers.TestController     : Real thread begins to execute!
2018-03-12 15:31:22.438  INFO 9916 --- [tTaskExecutor-1] c.l.t.b.e.controllers.TestController     : Real thread has been executed!

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

2.3 使用Async

在2.2中直接使用了taskExecutor来提交任务,这个时候还需要实现一个Runable的接口来实现具体的任务逻辑。实际上这个过程通过Async注解来进行简化。
Async注解用于表示方法需要异步调用,此时Spring会使用后台的线程池来异步的执行它所注解的方法;一般情况下这个方法的返回类型需要是void的;但也可以是Future类型的;当使用Future时即可对提交的任务执行情况进行判别。
Async注解也可用于类上,当用于类上时,相当于给所有的方法都默认加上了Async注解。

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

2.3.1 AsyncService

其实现如下: 

@Service
public class AsyncService {
    private static final Logger logger = LoggerFactory.getLogger(AsyncService.class);

    @Async
    public void testNoRespNoParamAsync() {
        logger.info("AsyncService begins to execute!");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            logger.error("AsyncService was interrupted!", e);
            return;
        }

        logger.info("AsyncService execution completed!");
    }
}

2.3.2 TestController

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

@Autowired
private AsyncService asyncService;

@RequestMapping("/testAsync")
public String testAsync() {
    logger.info("TestAsync begins to execute!");
    long startTime = System.currentTimeMillis();

    asyncService.testNoRespNoParamAsync();
    logger.info("TestAsync execution completed, use time: {}!", (System.currentTimeMillis() - startTime) / 1000);

    return "End!";
}

2.3.4 添加EnableAsync

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

@EnableAsync
@SpringBootApplication
public class EShopApplication {

    public static void main(String[] args) {
        SpringApplication.run(EShopApplication.class, args);
    }
}

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

2018-03-12 16:03:13.974  INFO 9916 --- [-nio-80-exec-10] c.l.t.b.e.controllers.TestController     : TestAsync begins to execute!
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!
2018-03-12 16:03:14.030  INFO 9916 --- [tTaskExecutor-1] c.l.t.b.eshop.service.AsyncService       : AsyncService begins to execute!
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。

3. 参考资料

  1. https://docs.spring.io/spring/docs/5.0.5.BUILD-SNAPSHOT/spring-framework-reference/integration.html#scheduling

你可能感兴趣的:(Spring)