@Async 註解的使用

在 Spring 中,@Async 標註的方法,在執行的時候,是異步運行的,它運行在獨立的線程中,程序不會被該方法所阻塞。

使用的時候,需要通過註解@EnableAsync 打開配置,表示可以運行異步的方法。

Java

@Configuration @EnableAsync public class Config { }

1234

@Configuration  @EnableAsync  public class Config {}

在異步的方法上面,標註上 @Async 註解即表示該方法是異步運行的。不過需要注意,該方法必須是 public 的,而且,在自身類中,調用異步方法是無效的。

實現異步方法的步驟如下: 第一步,配置文件中,標註可以使用異步@EnableAsync

Java

@Configuration @ComponentScan(value = "com.learn") @EnableAsync public class Config { }

123456

@Configuration@ComponentScan(value = "com.learn")@EnableAsyncpublic class Config { }

第二步,實現異步方法,通過@Async 註解。

返回值

實現異步方法有兩種類型,一種是無返回值,一種是有返回值。

無返回值的話,和常規寫法沒什麼不同,但是有返回值的話,需要將返回值包在 Future 對象中。Future 對象是專門存放異步響應的一個接口。

演示代碼如下:

Java

@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); } }

1234567891011121314151617181920212223242526272829

@Componentpublic 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); }}

第三步,調用異步方法。

Java

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(); }

12345678910111213141516171819202122

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(); }

觀察結果:

Java

九月 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

12345678910111213

九月 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 2018Sat Sep 15 21:51:54 CST 2018calling asyncThing,MyAsync-1,Sat Sep 15 21:51:54 CST 2018asyncThing Finished,MyAsync-1,Sat Sep 15 21:51:56 CST 2018calling asyncSquare,MyAsync-1,Sat Sep 15 21:51:56 CST 2018asyncSquare Finished,MyAsync-1,Sat Sep 15 21:51:58 CST 20189

可以看到,異步方法是立即返回結果值,不會阻塞主線程的。默認的,打開異步開關後,Spring 會使用一個 SimpleAsyncTaskExecutor 作爲線程池,該線程默認的併發數是不受限制的。所以每次異步方法來,都會獲取一個新線程去運行它。

AsyncConfigurer 接口

Spring 4 中,對異步方法可以做一些配置,將配置類實現 AsyncConfigurer 接口後,可以實現自定義線程池的功能,和統一處理異步方法的異常。

如果不限制併發數,可能會造成系統壓力。AsyncConfigurer 接口中的方法 Executor getAsyncExecutor() 實現自定義線程池。控制併發數。

AsyncConfigurer 接口中的方法 public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() 用於處理異步方法的異常。

AsyncUncaughtExceptionHandler 接口,只有一個方法:

void handleUncaughtException(Throwable ex, Method method, Object… params);

因此,AsyncUncaughtExceptionHandler 接口可以認爲是一個函數式接口,可以用拉姆達表達式實現該接口。

演示代碼如下:

Java

@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); }

123456789101112131415161718192021222324

@Configuration@ComponentScan(value = "com.learn")@EnableAsyncpublic 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);  }

將其中一個異步方法,寫一行會產生異常的代碼:

Java

@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()); }

12345678910111213

@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 個線程,而且其中一個異步方法會有異常。

運行後的結果如下:

Java

九月 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

12345678910111213

九月 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 2018Sat Sep 15 21:52:48 CST 2018calling 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;@1626ab0bcalling asyncSquare,MyAsync-1,Sat Sep 15 21:52:50 CST 2018asyncSquare Finished,MyAsync-1,Sat Sep 15 21:52:52 CST 20189

可以看到,由於線程池中只有 1 個線程,所以兩個異步方法,使用的是同一個線程運行的。異常的處理也由 AsyncUncaughtExceptionHandler 接口處理掉了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章