Spring Boot如何優雅的使用多執行緒例項詳解
前言
本文帶你快速瞭解@Async註解的用法,包括非同步方法無返回值、有返回值,最後總結了@Async註解失效的幾個坑。
在 SpringBoot 應用中,經常會遇到在一個介面中,同時做事情1,事情2,事情3,如果同步執行的話,則本次介面時間取決於事情1 2 3執行時間之和;如果三件事同時執行,則本次介面時間取決於事情1 2 3執行時間最長的那個,合理使用多執行緒,可以大大縮短介面時間。那麼在 SpringBoot 應用中如何優雅的使用多執行緒呢?
Don't bb,show me code.
快速使用
SpringBoot應用中需要新增@EnableAsync註解,來開啟非同步呼叫,一般還會配置一個執行緒池,非同步的方法交給特定的執行緒池完成,如下:
@Configuration @EnableAsync public class AsyncConfiguration { @Bean("doSomethingExecutor") public Executor doSomethingExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // 核心執行緒數:執行緒池建立時候初始化的執行緒數 executor.setCorePoolSize(10); // 最大執行緒數:執行緒池最大的執行緒數,只有在緩衝佇列滿了之後才會申請超過核心執行緒數的執行緒 executor.setMaxPoolSize(20); // 緩衝佇列:用來緩衝執行任務的佇列 executor.setQueueCapacity(500); // 允許執行緒的空閒時間60秒:當超過了核心執行緒之外的執行緒在空閒時間到達之後會被銷燬 executor.setKeepAliveSeconds(60); // 執行緒池名的字首:設定好了之後可以方便我們定位處理任務所在的執行緒池 executor.setThreadNamePrefix("do-something-"); // 緩衝佇列滿了之後的拒絕策略:由呼叫執行緒處理(一般是主執行緒) executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy()); executor.initialize(); return executor; } }
使用的方式非常簡單,在需要非同步的方法上加@Async註解
@RestController public class AsyncController { @Autowired private AsyncService asyncService; @GetMapping("/open/something") public String something() { int count = 10; for (int i = 0; i < count; i++) { asyncService.doSomething("index = " + i); } return "success"; } } @Slf4j @Service public class AsyncService { // 指定使用beanname為doSomethingExecutor的執行緒池 @Async("doSomethingExecutor") public String doSomething(String message) { log.info("do something,message={}",message); try { Thread.sleep(1000); } catch (InterruptedException e) { log.error("do something error: ",e); } return message; } }
訪問:127.0.0.1:8080/open/something,日誌如下
2020-04-19 23:42:42.486 INFO 21168 --- [io-8200-exec-17] x.g.b.system.controller.AsyncController : do something end,time 8 milliseconds
2020-04-19 23:42:42.488 INFO 21168 --- [ do-something-1] x.gits.boot.system.service.AsyncService : do something,message=index = 0
2020-04-19 23:42:42.488 INFO 21168 --- [ do-something-5] x.gits.boot.system.service.AsyncService : do something,message=index = 4
2020-04-19 23:42:42.488 INFO 21168 --- [ do-something-4] x.gits.boot.system.service.AsyncService : do something,message=index = 3
2020-04-19 23:42:42.488 INFO 21168 --- [ do-something-6] x.gits.boot.system.service.AsyncService : do something,message=index = 5
2020-04-19 23:42:42.488 INFO 21168 --- [ do-something-9] x.gits.boot.system.service.AsyncService : do something,message=index = 8
2020-04-19 23:42:42.488 INFO 21168 --- [ do-something-8] x.gits.boot.system.service.AsyncService : do something,message=index = 7
2020-04-19 23:42:42.488 INFO 21168 --- [do-something-10] x.gits.boot.system.service.AsyncService : do something,message=index = 9
2020-04-19 23:42:42.488 INFO 21168 --- [ do-something-7] x.gits.boot.system.service.AsyncService : do something,message=index = 6
2020-04-19 23:42:42.488 INFO 21168 --- [ do-something-2] x.gits.boot.system.service.AsyncService : do something,message=index = 1
2020-04-19 23:42:42.488 INFO 21168 --- [ do-something-3] x.gits.boot.system.service.AsyncService : do something,message=index = 2
由此可見已經達到非同步執行的效果了,並且使用到了咱們配置的執行緒池。
獲取非同步方法返回值
當非同步方法有返回值時,如何獲取非同步方法執行的返回結果呢?這時需要非同步呼叫的方法帶有返回值CompletableFuture。
CompletableFuture是對Feature的增強,Feature只能處理簡單的非同步任務,而CompletableFuture可以將多個非同步任務進行復雜的組合。如下:
@RestController public class AsyncController { @Autowired private AsyncService asyncService; @SneakyThrows @ApiOperation("非同步 有返回值") @GetMapping("/open/somethings") public String somethings() { CompletableFuture<String> createOrder = asyncService.doSomething1("create order"); CompletableFuture<String> reduceAccount = asyncService.doSomething2("reduce account"); CompletableFuture<String> saveLog = asyncService.doSomething3("save log"); // 等待所有任務都執行完 CompletableFuture.allOf(createOrder,reduceAccount,saveLog).join(); // 獲取每個任務的返回結果 String result = createOrder.get() + reduceAccount.get() + saveLog.get(); return result; } } @Slf4j @Service public class AsyncService { @Async("doSomethingExecutor") public CompletableFuture<String> doSomething1(String message) throws InterruptedException { log.info("do something1: {}",message); Thread.sleep(1000); return CompletableFuture.completedFuture("do something1: " + message); } @Async("doSomethingExecutor") public CompletableFuture<String> doSomething2(String message) throws InterruptedException { log.info("do something2: {}",message); Thread.sleep(1000); return CompletableFuture.completedFuture("; do something2: " + message); } @Async("doSomethingExecutor") public CompletableFuture<String> doSomething3(String message) throws InterruptedException { log.info("do something3: {}",message); Thread.sleep(1000); return CompletableFuture.completedFuture("; do something3: " + message); } }
訪問介面
C:\Users\Administrator>curl -X GET "http://localhost:8200/open/somethings" -H "accept: */*" do something1: create order; do something2: reduce account; do something3: save log
控制檯上關鍵日誌如下:
2020-04-20 00:27:42.238 INFO 5672 --- [ do-something-3] x.gits.boot.system.service.AsyncService : do something3: save log
2020-04-20 00:27:42.238 INFO 5672 --- [ do-something-2] x.gits.boot.system.service.AsyncService : do something2: reduce account
2020-04-20 00:27:42.238 INFO 5672 --- [ do-something-1] x.gits.boot.system.service.AsyncService : do something1: create order
注意事項
@Async註解會在以下幾個場景失效,也就是說明明使用了@Async註解,但就沒有走多執行緒。
- 非同步方法使用static關鍵詞修飾;
- 非同步類不是一個Spring容器的bean(一般使用註解@Component和@Service,並且能被Spring掃描到);
- SpringBoot應用中沒有新增@EnableAsync註解;
- 在同一個類中,一個方法呼叫另外一個有@Async註解的方法,註解不會生效。原因是@Async註解的方法,是在代理類中執行的。
需要注意的是:
非同步方法使用註解@Async的返回值只能為void或者Future及其子類,當返回結果為其他型別時,方法還是會非同步執行,但是返回值都是null,部分原始碼如下:
AsyncExecutionInterceptor#invoke
通過上邊幾個示例,@Async實際還是通過Future或CompletableFuture來非同步執行的,Spring又封裝了一下,讓我們使用的更方便。
本文示例程式碼:gitee.com/songyinyin/… ,搜尋AsyncController可得。
總結
到此這篇關於Spring Boot如何優雅的使用多執行緒的文章就介紹到這了,更多相關Spring Boot優雅使用多執行緒內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!