SpringBoot異步調用Async,怎么搞?
點擊上方藍色字體,選擇“標星公眾號”
優(yōu)質文章,第一時間送達
? 作者?|??47號Gamer丶
來源 |? urlify.cn/qMreiy
異步調用相對于同步調用而言,通常的方法都是程序按照順序來執(zhí)行的,程序的每一步都需要等到上一步執(zhí)行完成之后才能繼續(xù)往下執(zhí)行;而異步調用則無需等待,它可以在不阻塞主線程的情況下執(zhí)行高耗時方法
實現(xiàn)異步調用
1、在主類中添加@EnableAsync注解
@EnableAsync
@SpringBootApplication
public?class?WebMvcApplication?{
????public?static?void?main(String[]?args){
????????SpringApplication?app?=?new?SpringApplication(WebMvcApplication.class);
????????app.run(args);
????}
}
2、創(chuàng)建一個AsyncTask類
在里面添加兩個用@Async注解的task
@Component
@Slf4j
public?class?AsyncTask?{
????@Async
????public?Future?doTask1()?throws?InterruptedException{
????????log.info("Task1?started.");
????????long?start?=?System.currentTimeMillis();
????????Thread.sleep(5000);
????????long?end?=?System.currentTimeMillis();
????????log.info("Task1?finished,?time?elapsed:?{}?ms.",?end-start);
????????return?new?AsyncResult<>("Task1?accomplished!");
????}
????@Async
????public?Future?doTask2()?throws?InterruptedException{
????????log.info("Task2?started.");
????????long?start?=?System.currentTimeMillis();
????????Thread.sleep(3000);
????????long?end?=?System.currentTimeMillis();
????????log.info("Task2?finished,?time?elapsed:?{}?ms.",?end-start);
????????return?new?AsyncResult<>("Task2?accomplished!");
????}
}
調用:
public?class?AsyncController?{
????@Autowired
????private?AsyncTask?asyncTask;
????@RequestMapping(value?=?"/async",method?=?RequestMethod.GET)
????public?String?task()?throws?InterruptedException,?ExecutionException?{
????????Long?time?=?System.currentTimeMillis();
????????Future?task1?=?asyncTask.doTask1();
????????Future?task2?=?asyncTask.doTask2();
????????while(true)?{
????????????if(task1.isDone()?&&?task2.isDone())?{
????????????????log.info("Task1?result:?{}",?task1.get());
????????????????log.info("Task2?result:?{}",?task2.get());
????????????????break;
????????????}
????????????Thread.sleep(1000);
????????}
????????log.info("耗時:{}?ms",System.currentTimeMillis()-time);
????????return?"success";
????}
}
Future接口:
用于獲取異步計算的結果,可通過get()獲取結果、cancel()取消、isDone()判斷是否完成等操作。
V get():獲取結果,若無結果會阻塞至異步計算完成
V get(long timeOut, TimeUnit unit):獲取結果,超時返回null
boolean isDone():執(zhí)行結束(完成/取消/異常)返回true
boolean isCancelled():任務完成前被取消返回true
boolean cancel(boolean mayInterruptRunning):取消任務,未開始或已完成返回false,參數(shù)表示是否中斷執(zhí)行中的線程。Future.cancel(true)適用于長時間處于運行的任務,并且能夠處理interruption 。
執(zhí)行結果:
2019-12-20?15:52:44.685??INFO?9240?---?[?????????task-2]:?Task2?started.
2019-12-20?15:52:44.685??INFO?9240?---?[?????????task-1]:?Task1?started.
2019-12-20?15:52:47.685??INFO?9240?---?[?????????task-2]:?Task2?finished,?time?elapsed:?3000?ms.
2019-12-20?15:52:49.685??INFO?9240?---?[?????????task-1]:?Task1?finished,?time?elapsed:?5000?ms.
2019-12-20?15:52:49.686??INFO?9240?---?[nio-7091-exec-1]:?Task1?result:?Task1?accomplished!
2019-12-20?15:52:49.686??INFO?9240?---?[nio-7091-exec-1]:?Task2?result:?Task2?accomplished!
2019-12-20?15:52:49.686??INFO?9240?---?[nio-7091-exec-1]:?耗時:5007?ms
自定義的Executor:
@Configuration
@EnableAsync
public?class?ExecutorConfig?{
????/**?Set?the?ThreadPoolExecutor's?core?pool?size.?*/
????private?int?corePoolSize?=?10;
????/**?Set?the?ThreadPoolExecutor's?maximum?pool?size.?*/
????private?int?maxPoolSize?=?200;
????/**?Set?the?capacity?for?the?ThreadPoolExecutor's?BlockingQueue.?*/
????private?int?queueCapacity?=?10;
????@Bean("taskExecutor")
????public?Executor?taskExecutor()?{
????????ThreadPoolTaskExecutor?executor?=?new?ThreadPoolTaskExecutor();
????????executor.setCorePoolSize(corePoolSize);
????????executor.setMaxPoolSize(maxPoolSize);
????????executor.setQueueCapacity(queueCapacity);
????????executor.setThreadNamePrefix("default-");
????????executor.initialize();
????????return?executor;
????}
????@Bean
????public?Executor?myAsync()?{
????????ThreadPoolTaskExecutor?executor?=?new?ThreadPoolTaskExecutor();
????????executor.setCorePoolSize(corePoolSize);
????????executor.setMaxPoolSize(maxPoolSize);
????????executor.setQueueCapacity(queueCapacity);
????????executor.setKeepAliveSeconds(60);
????????executor.setThreadNamePrefix("MyExecutor-");
????????// rejection-policy:當pool已經(jīng)達到max size的時候,如何處理新任務
????????// CALLER_RUNS:不在新線程中執(zhí)行任務,而是有調用者所在的線程來執(zhí)行
????????executor.setRejectedExecutionHandler(new?ThreadPoolExecutor.CallerRunsPolicy());
????????executor.initialize();
????????return?executor;
????}
}
核心線程數(shù)10:線程池創(chuàng)建時候初始化的線程數(shù)
最大線程數(shù)200:線程池最大的線程數(shù),只有在緩沖隊列滿了之后才會申請超過核心線程數(shù)的線程
緩沖隊列10:用來緩沖執(zhí)行任務的隊列
允許線程的空閑時間60秒:當超過了核心線程出之外的線程在空閑時間到達之后會被銷毀
線程池名的前綴:設置好了之后可以方便我們定位處理任務所在的線程池
線程池對拒絕任務的處理策略:這里采用了CallerRunsPolicy策略,當線程池沒有處理能力的時候,該策略會直接在 execute 方法的調用線程中運行被拒絕的任務;如果執(zhí)行程序已關閉,則會丟棄該任務
自定義Executor的類名,放進@Async注解中:
public?class?AsyncTask?{
????@Async("myAsync")
????public?Future?doTask1()?throws?InterruptedException{
????}
????@Async
????public?Future?doTask2()?throws?InterruptedException{
????}
}
沒類加類名的@Async注解,會使用 @Bean(“taskExecutor”)的Executor。
需要注意的問題:
需要注意的問題一:異步方法的定義位置問題
最好將異步調用的方法單獨放在一個@Component類中,或者說不要將異步調用方法寫在@Controller中,否則將無法進行調用,因為SpringBoot使用@Transaction需要經(jīng)過事務攔截器,只有通過了該事務攔截器的方法才能被加入Spring的事務管理器中,而在同一個類中的一個方法調用另一個方法只會經(jīng)過一次事務攔截器,所以如果是后面的方法使用了事務注解將不會生效,在這里異步調用也是同樣的道理
需要注意的問題二:異步方法的事務調用問題
在@Async注解的方法上再使用@Transaction注解是無效的,在@Async注解的方法中調用Service層的事務方法是有效的
需要注意的問題三:異步方法必須是實例的
因為靜態(tài)方法不能被override重寫,因為@Async異步方法的實現(xiàn)原理是通過注入一個代理類到Bean中,該代理類集成這個Bean并且需要重寫這個異步方法,所以需要是實例方法
粉絲福利:108本java從入門到大神精選電子書領取
???
?長按上方鋒哥微信二維碼?2 秒 備注「1234」即可獲取資料
感謝點贊支持下哈?
