在做项目过程中,一些耗时长的任务可能需要在后台线程池中运行;典型的如发送邮件等,由于需要调用外部的接口来进行实际的发送操作,如果客户端在提交发送请求后一直等待服务器端发送成功后再返回,就会长时间的占用服务器的一个连接;当这类请求过多时,服务器连接数会不够用,新的连接请求可能无法得到满足,从而导致客户端连接失败。因此这类服务一般需要使用到后台线程池来处理。
在这种情况下,我们可以直接使用concurrent包中的线程池来处理,也可以使用其它的方案如Quartz等组件中的线程池来解决;为适配这些不同的方案,Spring引入了TaskExecutor接口作为顶层接口,并提供了几种不同的实现来满足不同的场景。
本文目录:
Spring包含了以下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来使用。
使用示例较为简单,直接在一个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.2中直接使用了taskExecutor来提交任务,这个时候还需要实现一个Runable的接口来实现具体的任务逻辑。实际上这个过程通过Async注解来进行简化。
Async注解用于表示方法需要异步调用,此时Spring会使用后台的线程池来异步的执行它所注解的方法;一般情况下这个方法的返回类型需要是void的;但也可以是Future类型的;当使用Future时即可对提交的任务执行情况进行判别。
Async注解也可用于类上,当用于类上时,相当于给所有的方法都默认加上了Async注解。
现在来实现一个AsyncService类,用于演示Async的使用:
其实现如下:
@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!");
}
}
在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!";
}
在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。
- https://docs.spring.io/spring/docs/5.0.5.BUILD-SNAPSHOT/spring-framework-reference/integration.html#scheduling