在 Spring 中,@Async 标注的方法,在执行的时候,是异步运行的,它运行在独立的线程中,程序不会被该方法所阻塞。
使用的时候,需要通过注解@EnableAsync 打开配置,表示可以运行异步的方法。
@Configuration
@EnableAsync
public class Config {
}
在异步的方法上面,标注上 @Async 注解即表示该方法是异步运行的。不过需要注意,该方法必须是 public 的,而且,在自身类中,调用异步方法是无效的。
实现异步方法的步骤如下: 第一步,配置文件中,标注可以使用异步@EnableAsync
@Configuration
@ComponentScan(value = "com.learn")
@EnableAsync
public class Config {
}
第二步,实现异步方法,通过@Async 注解。
返回值
实现异步方法有两种类型,一种是无返回值,一种是有返回值。
无返回值的话,和常规写法没什么不同,但是有返回值的话,需要将返回值包在 Future 对象中。Future 对象是专门存放异步响应的一个接口。
演示代码如下:
@Component
public class AsyncDemo {
@Async
public void asyncThing() {
System.out.println("calling asyncThing," + Thread.currentThread().getName() + "," + new Date());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
int i=100/0;
System.out.println("asyncThing Finished," + Thread.currentThread().getName() + "," + new Date());
}
@Async
public Future<Integer> asyncSquare(int x) {
System.out.println("calling asyncSquare," + Thread.currentThread().getName() + "," + new Date());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("asyncSquare Finished," + Thread.currentThread().getName() + "," + new Date());
return new AsyncResult<Integer>(x*x);
}
}
第三步,调用异步方法。
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Config.class);
AsyncDemo bean = applicationContext.getBean(AsyncDemo.class);
System.out.println(new Date());
bean.asyncThing();
Future<Integer> future_int=bean.asyncSquare(3);
System.out.println(new Date());
try {
System.out.println(future_int.get());
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
//防止主进程立即运行完毕,延迟10秒退出主进程
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
applicationContext.close();
}
观察结果:
九月 15, 2018 9:51:53 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@533ddba: startup date [Sat Sep 15 21:51:53 CST 2018]; root of context hierarchy
九月 15, 2018 9:51:54 下午 org.springframework.context.support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker postProcessAfterInitialization
信息: Bean 'config' of type [com.learn.Config$$EnhancerBySpringCGLIB$$31aaeebc] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
九月 15, 2018 9:51:54 下午 org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor initialize
信息: Initializing ExecutorService
Sat Sep 15 21:51:54 CST 2018
Sat Sep 15 21:51:54 CST 2018
calling asyncThing,MyAsync-1,Sat Sep 15 21:51:54 CST 2018
asyncThing Finished,MyAsync-1,Sat Sep 15 21:51:56 CST 2018
calling asyncSquare,MyAsync-1,Sat Sep 15 21:51:56 CST 2018
asyncSquare Finished,MyAsync-1,Sat Sep 15 21:51:58 CST 2018
9
可以看到,异步方法是立即返回结果值,不会阻塞主线程的。默认的,打开异步开关后,Spring 会使用一个 SimpleAsyncTaskExecutor 作为线程池,该线程默认的并发数是不受限制的。所以每次异步方法来,都会获取一个新线程去运行它。
AsyncConfigurer 接口
Spring 4 中,对异步方法可以做一些配置,将配置类实现 AsyncConfigurer 接口后,可以实现自定义线程池的功能,和统一处理异步方法的异常。
如果不限制并发数,可能会造成系统压力。AsyncConfigurer 接口中的方法 Executor getAsyncExecutor() 实现自定义线程池。控制并发数。
AsyncConfigurer 接口中的方法 public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() 用于处理异步方法的异常。
AsyncUncaughtExceptionHandler 接口,只有一个方法:
void handleUncaughtException(Throwable ex, Method method, Object… params);
因此,AsyncUncaughtExceptionHandler 接口可以认为是一个函数式接口,可以用拉姆达表达式实现该接口。
演示代码如下:
@Configuration
@ComponentScan(value = "com.learn")
@EnableAsync
public class Config implements AsyncConfigurer {
//自定义线程池,控制并发数,将线程池的大小设置成只有1个线程。
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();
threadPool.setCorePoolSize(1);
threadPool.setMaxPoolSize(1);
threadPool.setWaitForTasksToCompleteOnShutdown(true);
threadPool.setAwaitTerminationSeconds(60 * 15);
threadPool.setThreadNamePrefix("MyAsync-");
threadPool.initialize();
return threadPool;
}
//统一处理异常
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (throwable, method, objects) -> System.out.println(
"-- exception handler -- " + throwable + "-- method -- " + method + "-- objects -- " + objects);
}
将其中一个异步方法,写一行会产生异常的代码:
@Async
public void asyncThing() {
System.out.println("calling asyncThing," + Thread.currentThread().getName() + "," + new Date());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int i=100/0;//抛出异常
System.out.println("asyncThing Finished," + Thread.currentThread().getName() + "," + new Date());
}
如上代码,演示中,将线程池的大小设置成只有 1 个线程,而且其中一个异步方法会有异常。
运行后的结果如下:
九月 15, 2018 9:52:47 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@533ddba: startup date [Sat Sep 15 21:52:47 CST 2018]; root of context hierarchy
九月 15, 2018 9:52:48 下午 org.springframework.context.support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker postProcessAfterInitialization
信息: Bean 'config' of type [com.learn.Config$$EnhancerBySpringCGLIB$$31aaeebc] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
九月 15, 2018 9:52:48 下午 org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor initialize
信息: Initializing ExecutorService
Sat Sep 15 21:52:48 CST 2018
Sat Sep 15 21:52:48 CST 2018
calling asyncThing,MyAsync-1,Sat Sep 15 21:52:48 CST 2018
-- exception handler -- java.lang.ArithmeticException: / by zero-- method -- public void com.learn.entity.AsyncDemo.asyncThing()-- objects -- [Ljava.lang.Object;@1626ab0b
calling asyncSquare,MyAsync-1,Sat Sep 15 21:52:50 CST 2018
asyncSquare Finished,MyAsync-1,Sat Sep 15 21:52:52 CST 2018
9
可以看到,由于线程池中只有 1 个线程,所以两个异步方法,使用的是同一个线程运行的。异常的处理也由 AsyncUncaughtExceptionHandler 接口处理掉了。