衆所周知,java的代碼是同步順序執行,當咱們須要執行異步操做時咱們須要建立一個新線程去執行,以往咱們是這樣操做的:html
/** * 任務類 */ class Task implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName() + ":異步任務"); } }
//新建線程並執行任務類 new Thread(new Task()).start();
jdk1.8以後可使用Lambda 表達式java
//新建線程並執行任務類 new Thread(() -> { System.out.println(Thread.currentThread().getName() + ":異步任務"); }).start();
固然,除了顯式的new Thread,咱們通常經過線程池獲取線程,這裏就再也不展開mysql
Spring 3.0以後提供了一個@Async註解,使用@Async註解進行優雅的異步調用,咱們先看一下API對這個註解的定義:https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/scheduling/annotation/Async.htmlgit
本文記錄在SpringBoot項目中使用@Async註解,實現優雅的異步調用github
項目工程結構spring
由於要測試事務,因此須要引入sql
<!--添加springdata-jpa依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!--添加MySQL驅動依賴 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>
在啓動類開啓啓用異步調用,同時注入ApplicationRunner對象在啓動類進行調用測試api
package cn.huanzi.qch.springbootasync; import cn.huanzi.qch.springbootasync.service.TestService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.ApplicationRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.stereotype.Component; @Component @EnableAsync//開啓異步調用 @SpringBootApplication public class SpringbootAsyncApplication { @Autowired private TestService testService; public static void main(String[] args) { SpringApplication.run(SpringbootAsyncApplication.class, args); } /** * 啓動成功 */ @Bean public ApplicationRunner applicationRunner() { return applicationArguments -> { long startTime = System.currentTimeMillis(); System.out.println(Thread.currentThread().getName() + ":開始調用異步業務"); //無返回值 // testService.asyncTask(); //有返回值,但主線程不須要用到返回值 // Future<String> future = testService.asyncTask("huanzi-qch"); //有返回值,且主線程須要用到返回值 // System.out.println(Thread.currentThread().getName() + ":返回值:" + testService.asyncTask("huanzi-qch").get()); //事務測試,事務正常提交 // testService.asyncTaskForTransaction(false); //事務測試,模擬異常事務回滾 // testService.asyncTaskForTransaction(true); long endTime = System.currentTimeMillis(); System.out.println(Thread.currentThread().getName() + ":調用異步業務結束,耗時:" + (endTime - startTime)); }; } }
看一下咱們的測試業務類TestServicespringboot
package cn.huanzi.qch.springbootasync.service; import java.util.concurrent.Future; public interface TestService { /** * 異步調用,無返回值 */ void asyncTask(); /** * 異步調用,有返回值 */ Future<String> asyncTask(String s); /** * 異步調用,無返回值,事務測試 */ void asyncTaskForTransaction(Boolean exFlag); }
package cn.huanzi.qch.springbootasync.service; import cn.huanzi.qch.springbootasync.pojo.TbUser; import cn.huanzi.qch.springbootasync.repository.TbUserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.AsyncResult; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.concurrent.Future; @Service public class TestServiceImpl implements TestService { @Autowired private TbUserRepository tbUserRepository; @Async @Override public void asyncTask() { long startTime = System.currentTimeMillis(); try { //模擬耗時 Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } long endTime = System.currentTimeMillis(); System.out.println(Thread.currentThread().getName() + ":void asyncTask(),耗時:" + (endTime - startTime)); } @Async("asyncTaskExecutor") @Override public Future<String> asyncTask(String s) { long startTime = System.currentTimeMillis(); try { //模擬耗時 Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } long endTime = System.currentTimeMillis(); System.out.println(Thread.currentThread().getName() + ":Future<String> asyncTask(String s),耗時:" + (endTime - startTime)); return AsyncResult.forValue(s); } @Async("asyncTaskExecutor") @Transactional @Override public void asyncTaskForTransaction(Boolean exFlag) { //新增一個用戶 TbUser tbUser = new TbUser(); tbUser.setUsername("huanzi-qch"); tbUser.setPassword("123456"); tbUserRepository.save(tbUser); if(exFlag){ //模擬異常 throw new RuntimeException("模擬異常"); } } }
package cn.huanzi.qch.springbootasync.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; /** * 線程池的配置 */ @Configuration public class AsyncConfig { private static final int MAX_POOL_SIZE = 50; private static final int CORE_POOL_SIZE = 20; @Bean("asyncTaskExecutor") public AsyncTaskExecutor asyncTaskExecutor() { ThreadPoolTaskExecutor asyncTaskExecutor = new ThreadPoolTaskExecutor(); asyncTaskExecutor.setMaxPoolSize(MAX_POOL_SIZE); asyncTaskExecutor.setCorePoolSize(CORE_POOL_SIZE); asyncTaskExecutor.setThreadNamePrefix("async-task-thread-pool-"); asyncTaskExecutor.initialize(); return asyncTaskExecutor; } }
配置好後,@Async會默認從線程池獲取線程,固然也能夠顯式的指定@Async("asyncTaskExecutor")app
/** * 啓動成功 */ @Bean public ApplicationRunner applicationRunner() { return applicationArguments -> { long startTime = System.currentTimeMillis(); System.out.println(Thread.currentThread().getName() + ":開始調用異步業務");
//無返回值 testService.asyncTask();
long endTime = System.currentTimeMillis(); System.out.println(Thread.currentThread().getName() + ":調用異步業務結束,耗時:" + (endTime - startTime)); }; }
有返回值,但主線程不須要用到返回值
/** * 啓動成功 */ @Bean public ApplicationRunner applicationRunner() { return applicationArguments -> { long startTime = System.currentTimeMillis(); System.out.println(Thread.currentThread().getName() + ":開始調用異步業務");//有返回值,但主線程不須要用到返回值 Future<String> future = testService.asyncTask("huanzi-qch"); long endTime = System.currentTimeMillis(); System.out.println(Thread.currentThread().getName() + ":調用異步業務結束,耗時:" + (endTime - startTime)); }; }
有返回值,且主線程須要用到返回值
/** * 啓動成功 */ @Bean public ApplicationRunner applicationRunner() { return applicationArguments -> { long startTime = System.currentTimeMillis(); System.out.println(Thread.currentThread().getName() + ":開始調用異步業務"); //有返回值,且主線程須要用到返回值 System.out.println(Thread.currentThread().getName() + ":返回值:" + testService.asyncTask("huanzi-qch").get()); long endTime = System.currentTimeMillis(); System.out.println(Thread.currentThread().getName() + ":調用異步業務結束,耗時:" + (endTime - startTime)); }; }
能夠發現,有返回值的狀況下,雖然異步業務邏輯是由新線程執行,但若是在主線程操做返回值對象,主線程會等待,仍是順序執行
爲了方便觀察、測試,咱們在配置文件中將日誌級別設置成debug
#修改日誌登記,方便調試
logging.level.root=debug
事務提交
/** * 啓動成功 */ @Bean public ApplicationRunner applicationRunner() { return applicationArguments -> { long startTime = System.currentTimeMillis(); System.out.println(Thread.currentThread().getName() + ":開始調用異步業務");//事務測試,事務正常提交 testService.asyncTaskForTransaction(false); long endTime = System.currentTimeMillis(); System.out.println(Thread.currentThread().getName() + ":調用異步業務結束,耗時:" + (endTime - startTime)); }; }
模擬異常,事務回滾
/** * 啓動成功 */ @Bean public ApplicationRunner applicationRunner() { return applicationArguments -> { long startTime = System.currentTimeMillis(); System.out.println(Thread.currentThread().getName() + ":開始調用異步業務"); //事務測試,模擬異常事務回滾 testService.asyncTaskForTransaction(true); long endTime = System.currentTimeMillis(); System.out.println(Thread.currentThread().getName() + ":調用異步業務結束,耗時:" + (endTime - startTime)); }; }
SpringBoot使用@Async優雅的異步調用就暫時記錄到這裏,之後再進行補充
代碼已經開源、託管到個人GitHub、碼雲: