在做项目过程中,一些耗时长的任务可能需要在后台线程池中运行;典型的如发送邮件等,由于需要调用外部的接口来进行实际的发送操作,如果客户端在提交发送请求后一直等待服务器端发送成功后再返回,就会长时间的占用服务器的一个连接;当这类请求过多时,服务器连接数会不够用,新的连接请求可能无法得到满足,从而导致客户端连接失败。因此这类服务一般需要使用到后台线程池来处理。
在这种情况下,我们可以直接使用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:
@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来使用。
使用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中提交的任务,与主线程是在两个线程中。
使用Async
在2中直接使用了taskExecutor来提交任务,这个时候还需要实现一个Runable的接口来实现具体的任务逻辑。实际上这个过程通过Async注解来进行简化。
Async注解用于表示方法需要异步调用,此时Spring会使用后台的线程池来异步的执行它所注解的方法;一般情况下这个方法的返回类型需要是void的;但也可以是Future类型的;当使用Future时即可对提交的任务执行情况进行判别。
Async注解也可用于类上,当用于类上时,相当于给所有的方法都默认加上了Async注解。
现在来实现一个AsyncService类,用于演示Async的使用:
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!");
}
}
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!";
}
添加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。