之前我们开始了在Spring中使用任务执行器TaskExecutor
,这样对于在Spring应用中如何使用线程,我们就更熟悉了。
然而有时候使用任务执行器可能有些啰嗦,特别是我们只需要执行一个简单的动作时。
这时候Spring的异步方法就派上用场了。
相比把任务执行器TaskExecutor
和可运行任务Runnable
混到一起,你可以交出执行器的控制权,换做使用简单的异步功能。
为了在另外一个线程中执行你的功能,所有你需要做的事情是使用@Async
注解你的功能。
Spring异步方法的使用有两种模式。
“发后即忘”模式 (fire and forget mode):一个没有返回值的方法。
@Async
@Transactional
public void printEmployees() {
List<Employee> employees = entityManager.
createQuery("SELECT e FROM Employee e").getResultList();
employees.stream().forEach(e->System.out.println(e.getEmail()));
}
“结果提取”模式(results retrieval mode):返回一个Future
对象的方法。
@Async
@Transactional
public CompletableFuture<List<Employee>> fetchEmployess() {
List<Employee> employees = entityManager.
createQuery("SELECT e FROM Employee e").getResultList();
return CompletableFuture.completedFuture(employees);
}
需要额外注意一下这个事实,如果通过this
来调用@Async
注解的方法,注解@Async
并不生效(译注:也就是说此时该方法会按照同步方式在当前线程执行)。这一点上,@Async
和@Transactional
表现类似。因此你需要将你的异步方法声明为public
(译注:声明为protected
或者private
是为了让子类或者自身实例使用,所以这里声明为public
才跟从外部被调用的目标一致。)。从AOP代理文档中你可以看到更多的信息。
Spring应用中仅仅使用@Async
注解想达到异步效果还不够,我们还需要配合使用另外一个Spring注解@EnableAsync
启用Spring异步方法执行能力,这个注解需要应用在某个配置类上,如下所示:
package com.gkatzioura.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
/**
* Created by gkatzioura on 4/26/17.
*/
@Configuration
@EnableAsync // 注意这个注解,在某个配置文件上使用该注解启用Spring异步执行能力
public class ThreadConfig {
@Bean
public TaskExecutor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(4);
executor.setMaxPoolSize(4);
executor.setThreadNamePrefix("sgfgd");
executor.initialize();
return executor;
}
}
下一个问题是,我们应该怎样声明异步功能将要使用的资源和线程池。从文档中我们可以得到答案:
缺省情况下,Spring会搜索一个关联的线程池定义:或者是上下文中定义的唯一一个
TaskExecutor
bean,或者是一个名字为”taskExecutor”的bean。如果两个都没有,就使用一个SimpleAsyncTaskExecutor
来处理异步方法调用。
然而有些情况下,我们不希望使用同一个线程池运行所有的任务。我们可能想要分开配置多个线程池来完成我们的异步任务。
想做到这一点,我们向注解@Async
传递我们想对每个功能任务使用的执行器的名字。
比如,我们配置了一个名字为‘specificTaskExecutor’ 的执行器 :
@Configuration
@EnableAsync
public class ThreadConfig {
// 定义一个名字为specificTaskExecutor的任务执行器线程池bean
@Bean(name = "specificTaskExecutor")
public TaskExecutor specificTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.initialize();
return executor;
}
}
然后可以这么使用它 , 通过@Async
中指定的执行器的限定名,异步功能会在指定的执行器中被执行 :
@Async("specificTaskExecutor")
public void runFromAnotherThreadPool() {
// 该方法的逻辑会在名字为`specificTaskExecutor`的任务执行器的某个线程上执行
System.out.println("你的异步任务");
}
下一篇文章我们会讲解线程上的事务。
你可以从github上找到本文中的代码。