在spring 3.x之后,内置了@Async,这个注解用于标注某个方法或某个类里面的所有方法都是需要异步处理的。被注解的方法被调用的时候,会在新线程中执行,而调用它的方法会在原来的线程中执行。这样可以避免阻塞、以及保证任务的实时性。适用于处理log、发送邮件、短信……等。
两种实现方式:注解和xml
一、通过注解实现
1. @Configuration
2. @EnableAsync
3. public class SpringConfig {
4.
5. /** Set the ThreadPoolExecutor's core pool size. */
6. private int corePoolSize = 10;
7. /** Set the ThreadPoolExecutor's maximum pool size. */
8. private int maxPoolSize = 200;
9. /** Set the capacity for the ThreadPoolExecutor's BlockingQueue. */
10. private int queueCapacity = 10;
11.
12. private String ThreadNamePrefix = "MyLogExecutor-";
13.
14. @Bean
15. public Executor logExecutor() {
16. new ThreadPoolTaskExecutor();
17. executor.setCorePoolSize(corePoolSize);
18. executor.setMaxPoolSize(maxPoolSize);
19. executor.setQueueCapacity(queueCapacity);
20. executor.setThreadNamePrefix(ThreadNamePrefix);
21.
22. // rejection-policy:当pool已经达到max size的时候,如何处理新任务
23. // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
24. new ThreadPoolExecutor.CallerRunsPolicy());
25. executor.initialize();
26. return executor;
27. }
28. }
1). 基于@Async无返回值调用
使用的方式非常简单,一个标注即可解决所有的问题:
1 @Async //标注使用
2 public void asyncMethodWithVoidReturnType() {
3 System.out.println("Execute method asynchronously. "
4 + Thread.currentThread().getName());
5 }
2). 基于@Async返回值的调用
1 @Async
2 public Future<String> asyncMethodWithReturnType() {
3 System.out.println("Execute method asynchronously - " + Thread.currentThread().getName());
4 try {
5 Thread.sleep(5000);
6 return new AsyncResult<String>("hello world !!!!");
7 } catch (InterruptedException e) {
8 //
9 }
10
11 return null;
12 }
以上示例可以发现,返回的数据类型为Future类型,其为一个接口。具体的结果类型为AsyncResult,这个是需要注意的地方。
调用返回结果的异步方法示例
1 public void testAsyncAnnotationForMethodsWithReturnType()
2 throws InterruptedException, ExecutionException {
3 System.out.println("Invoking an asynchronous method. " + Thread.currentThread().getName());
4 Future<String> future = asyncAnnotationExample.asyncMethodWithReturnType();
5
6 while (true) { ///这里使用了循环判断,等待获取结果信息
7 if (future.isDone()) { //判断是否执行完毕
8 System.out.println("Result from asynchronous process - " + future.get());
9 break;
10 }
11 System.out.println("Continue doing something else. ");
12 Thread.sleep(1000);
13 }
14 }
这些获取异步方法的结果信息,是通过不停的检查Future的状态来获取当前的异步方法是否执行完毕来实现的。
3). 基于@Async调用中的异常处理机制
在异步方法中,如果出现异常,对于调用者caller而言,是无法感知的。如果确实需要进行异常处理,则按照如下方法来进行处理:
1. 自定义实现AsyncTaskExecutor的任务执行器
在这里定义处理具体异常的逻辑和方式。
2. 配置由自定义的TaskExecutor替代内置的任务执行器
示例步骤1,自定义的TaskExecutor
1 public class ExceptionHandlingAsyncTaskExecutor implements AsyncTaskExecutor {
2 private AsyncTaskExecutor executor;
3 public ExceptionHandlingAsyncTaskExecutor(AsyncTaskExecutor executor) {
4 this.executor = executor;
5 }
6 用独立的线程来包装,@Async其本质就是如此
7 public void execute(Runnable task) {
8 executor.execute(createWrappedRunnable(task));
9 }
10 public void execute(Runnable task, long startTimeout) {
11 /用独立的线程来包装,@Async其本质就是如此
12 executor.execute(createWrappedRunnable(task), startTimeout);
13 }
14 public Future submit(Runnable task) { return executor.submit(createWrappedRunnable(task));
15 //用独立的线程来包装,@Async其本质就是如此。
16 }
17 public Future submit(final Callable task) {
18 //用独立的线程来包装,@Async其本质就是如此。
19 return executor.submit(createCallable(task));
20 }
21
22 private Callable createCallable(final Callable task) {
23 return new Callable() {
24 public T call() throws Exception {
25 try {
26 return task.call();
27 } catch (Exception ex) {
28 handle(ex);
29 throw ex;
30 }
31 }
32 };
33 }
34
35 private Runnable createWrappedRunnable(final Runnable task) {
36 return new Runnable() {
37 public void run() {
38 try {
39 task.run();
40 } catch (Exception ex) {
41 handle(ex);
42 }
43 }
44 };
45 }
46 private void handle(Exception ex) {
47 //具体的异常逻辑处理的地方
48 System.err.println("Error during @Async execution: " + ex);
49 }
50 }
分析: 可以发现其是实现了AsyncTaskExecutor, 用独立的线程来执行具体的每个方法操作。在createCallable和createWrapperRunnable中,定义了异常的处理方式和机制。
handle()就是未来我们需要关注的异常处理的地方。 配置文件中的内容:
1 <task:annotation-driven executor="exceptionHandlingTaskExecutor" scheduler="defaultTaskScheduler" />
2 <bean id="exceptionHandlingTaskExecutor" class="nl.jborsje.blog.examples.ExceptionHandlingAsyncTaskExecutor">
3 <constructor-arg ref="defaultTaskExecutor" />
4 </bean>
5 <task:executor id="defaultTaskExecutor" pool-size="5" />
6 <task:scheduler id="defaultTaskScheduler" pool-size="1" />
4). @Async调用中的事务处理机制
在@Async标注的方法,同时也适用了@Transactional进行了标注;在其调用数据库操作之时,将无法产生事务管理的控制,原因就在于其是基于异步处理的操作。
那该如何给这些操作添加事务管理呢?可以将需要事务管理操作的方法放置到异步方法内部,在内部被调用的方法上添加@Transactional.
例如: 方法A,使用了@Async/@Transactional来标注,但是无法产生事务控制的目的。
方法B,使用了@Async来标注, B中调用了C、D,C/D分别使用@Transactional做了标注,则可实现事务控制的目的。
二、通过xml实现
注解的应用范围:
- 类:表示这个类中的所有方法都是异步的
- 方法:表示这个方法是异步的,如果类也注解了,则以这个方法的注解为准
相关的配置:
<task:annotation-driven />配置:
- executor:指定一个缺省的executor给@Async使用。
例子:
<task:annotation-driven executor="asyncExecutor" />
<task:executor />配置参数:
- id:当配置多个executor时,被@Async("id")指定使用;也被作为线程名的前缀。
- pool-size:
- core size:最小的线程数,缺省:1
- max size:最大的线程数,缺省:Integer.MAX_VALUE
- queue-capacity:当最小的线程数已经被占用满后,新的任务会被放进queue里面,当这个queue的capacity也被占满之后,pool里面会创建新线程处理这个任务,直到总线程数达到了max size,这时系统会拒绝这个任务并抛出TaskRejectedException异常(缺省配置的情况下,可以通过rejection-policy来决定如何处理这种情况)。缺省值为:Integer.MAX_VALUE
- keep-alive:超过core size的那些线程,任务完成后,再经过这个时长(秒)会被结束掉
- rejection-policy:当pool已经达到max size的时候,如何处理新任务
- ABORT(缺省):抛出TaskRejectedException异常,然后不执行
- DISCARD:不执行,也不抛出异常
- DISCARD_OLDEST:丢弃queue中最旧的那个任务
- CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
配置例子:
<task:annotation-driven executor="asyncExecutor" />
100- 10000" queue-capacity=" 10"/>
实例:
1. <!-- 缺省的异步任务线程池 -->
2. <task:annotation-driven executor="asyncExecutor" />
3. <task:executor id="asyncExecutor" pool-size="100-10000" queue-capacity="10" />
4.
5. <!-- 处理log的线程池 -->
6. <task:executor id="logExecutor" pool-size="15-1000" queue-capacity="5" keep-alive="5"/>
1. @Override
2. @Async("logExecutor") //如果不指定名字,会使用缺省的“asyncExecutor”
3. public void saveUserOpLog(TabUserOpLog tabUserOpLog) {
4.
5. userOpLogDAO.insertTabUserOpLog(tabUserOpLog);
6. }
(注意:如果在同一个类中调用的话,不会生效,原因请参考:
)
通过log可以看到,已经分开两个线程执行:
线程的优先级和类型:
优先级:NORM_PRIORITY
类型:非守护线程
用户线程(User Thread):JVM会等待所有的用户线程结束后才退出;当系统中没有用户线程了,JVM也就退出了
守护线程(Daemon Thread):一般是为其他线程提供服务的线程,比如GC垃圾回收器;JVM退出时,不会管守护线程是否存在,而是直接退出
所以,对于文件、数据库的操作,不适宜使用守护线程,不然可能会丢失数据!
Web应用停止时,Spring容器会被关闭,调用者如果是Spring bean,就会停止生成新任务。然而,线程池中已经在运行的任务,由于缺省是用户线程,所以JVM会等待它们结束后才退出。