Spring Boot @Async 非同步任務執行

不要亂摸發表於2018-05-09

1、任務執行和排程

SpringTaskExecutorTaskScheduler介面提供了非同步執行和排程任務的抽象。

SpringTaskExecutorjava.util.concurrent.Executor介面時一樣的,這個介面只有一個方法execute(Runnable task)

1.1、TaskExecutor型別

Spring已經內建了許多TaskExecutor的實現,你沒有必要自己去實現:

  • SimpleAsyncTaskExecutor  這種實現不會重用任何執行緒,每次呼叫都會建立一個新的執行緒。
  • SyncTaskExecutor  這種實現不會非同步的執行
  • ConcurrentTaskExecutor  這種實現是java.util.concurrent.Executor的一個adapter
  • SimpleThreadPoolTaskExecutor  這種實現實際上是Quartz的SimpleThreadPool的一個子類,它監聽Spring的宣告週期回撥。
  • ThreadPoolTaskExecutor  這是最常用最通用的一種實現。它包含了java.util.concurrent.ThreadPoolExecutor的屬性,並且用TaskExecutor進行包裝。

1.2、註解支援排程和非同步執行

To enable support for @Scheduled and @Async annotations add @EnableScheduling and @EnableAsync to one of your @Configuration classes:

@Configuration
@EnableAsync
@EnableScheduling
public class AppConfig {
}

特別注意

The default advice mode for processing @Async annotations is "proxy" which allows for interception of calls through the proxy only; local calls within the same class cannot get intercepted that way. For a more advanced mode of interception, consider switching to "aspectj" mode in combination with compile-time or load-time weaving.

預設是用代理去處理@Async的,因此,相同類中的方法呼叫帶@Async的方法是無法非同步的,這種情況仍然是同步。

舉個例子:下面這種,在外部直接呼叫sayHi()是可以非同步執行的,而呼叫sayHello()時sayHi()仍然是同步執行

public class A {
  
    public void sayHello() {
        sayHi();
    }

    @Async
    public void sayHi() {

    }   
  
}

1.3、@Async註解

在方法上加@Async註解表示這是一個非同步呼叫。換句話說,方法的呼叫者會立即得到返回,並且實際的方法執行是想Spring的TaskExecutor提交了一個任務。

In other words, the caller will return immediately upon invocation and the actual execution of the method will occur in a task that has been submitted to a Spring TaskExecutor.

@Async
void doSomething() {
    // this will be executed asynchronously
}
@Async
void doSomething(String s) {
    // this will be executed asynchronously
}
@Async
Future<String> returnSomething(int i) {
    // this will be executed asynchronously
}

注意:

@Async methods may not only declare a regular java.util.concurrent.Future return type but also Spring’s org.springframework.util.concurrent.ListenableFuture or, as of Spring 4.2, JDK 8’s java.util.concurrent.CompletableFuture: for richer interaction with the asynchronous task and for immediate composition with further processing steps.

1.4、@Async限定Executor

預設情況下,當在方法上加@Async註解時,將會使用一個支援註解驅動的Executor。然而,@Async註解的value值可以指定一個別的Executor

@Async("otherExecutor")
void doSomething(String s) {
    // this will be executed asynchronously by "otherExecutor"
}

這裡,otherExecutor是Spring容器中任意Executor bean的名字。

1.5、@Async異常管理

當一個@Async方法有一個Future型別的返回值時,就很容易管理在調Future的get()方法獲取任務的執行結果時丟擲的異常。如果返回型別是void,那麼異常是不會被捕獲到的。

public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {

    @Override
    public void handleUncaughtException(Throwable ex, Method method, Object... params) {
        // handle exception
    }
}

 2、執行緒池配置

 1
 3 import org.springframework.context.annotation.Bean;
 4 import org.springframework.context.annotation.Configuration;
 5 import org.springframework.scheduling.annotation.EnableAsync;
 6 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 7 
 8 @Configuration
 9 @EnableAsync
10 public class TaskExecutorConfig {
11 
12     private Integer corePoolSize = 30;
13 
14     private Integer maxPoolSize = 50;
15 
16     private Integer keepAliveSeconds = 300;
17 
18 //    private Integer queueCapacity = 2000;
19 
20     @Bean("myThreadPoolTaskExecutor")
21     public ThreadPoolTaskExecutor myThreadPoolTaskExecutor() {
22         ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
23         executor.setCorePoolSize(corePoolSize);
24         executor.setMaxPoolSize(maxPoolSize);
25         executor.setKeepAliveSeconds(keepAliveSeconds);
26 //        executor.setQueueCapacity(queueCapacity);
27         executor.setWaitForTasksToCompleteOnShutdown(true);
28         executor.initialize();
29         return executor;
30     }
31 
32 }

呼叫

 1     @Async("myThreadPoolTaskExecutor")
 2     @Override
 3     public void present(CouponPresentLogEntity entity) {
 4         try {
 5             CouponBaseResponse rst = couponSendRpcService.send(entity.getUserId(), entity.getCouponBatchKey(), "1", entity.getVendorId());
 6             if (null != rst && rst.isSuccess()) {
 7                 entity.setStatus(PresentStatusEnum.SUCCESS.getType());
 8             }else {
 9                 String reason = (null == rst) ? "響應異常" : rst.getMsg();
10                 entity.setFailureReason(reason);
11                 entity.setStatus(PresentStatusEnum.FAILURE.getType());
12             }
13         }catch (Exception ex) {
14             log.error(ex.getMessage(), ex);
15             entity.setFailureReason(ex.getMessage());
16             entity.setStatus(PresentStatusEnum.FAILURE.getType());
17         }
18         couponPresentLogDao.update(entity);
19     }

結果

[INFO ] 2018-05-09 16:27:39.887 [myThreadPoolTaskExecutor-1] [com.ourhours.coupon.rpc.dubbo.ReceiveLogFilter] - receive method-name:send; arguments:[10046031,"4d7cc32f8f7e4b00bca56f6bf4b3b658","1",10001]
[INFO ] 2018-05-09 16:27:39.889 [myThreadPoolTaskExecutor-2] [com.ourhours.coupon.rpc.dubbo.ReceiveLogFilter] - receive method-name:send; arguments:[10046031,"4d7cc32f8f7e4b00bca56f6bf4b3b658","1",10001]

 

 

 參考:

Spring Framework Reference Documentation 4.3.17.RELEASE

 

相關文章