SpringBoot 如何實現非同步程式設計,老鳥們都這麼玩的!
映象下載、域名解析、時間同步請點選 阿里巴巴開源映象站
首先我們來看看在Spring中為什麼要使用非同步程式設計,它能解決什麼問題?
為什麼要用非同步框架,它解決什麼問題?
在SpringBoot的日常開發中,一般都是同步呼叫的。但實際中有很多場景非常適合使用非同步來處理,如:註冊新使用者,送100個積分;或下單成功,傳送push訊息等等。
就拿註冊新使用者這個用例來說,為什麼要非同步處理?
- 第一個原因:容錯性、健壯性,如果送積分出現異常,不能因為送積分而導致使用者註冊失敗;因為使用者註冊是主要功能,送積分是次要功能,即使送積分異常也要提示使用者註冊成功,然後後面在針對積分異常做補償處理。
- 第二個原因:提升效能,例如註冊使用者花了20毫秒,送積分花費50毫秒,如果用同步的話,總耗時70毫秒,用非同步的話,無需等待積分,故耗時20毫秒。
故,非同步能解決2個問題,效能和容錯性。
SpringBoot如何實現非同步呼叫?
對於非同步方法呼叫,從Spring3開始提供了@Async註解,我們只需要在方法上標註此註解,此方法即可實現非同步呼叫。
當然,我們還需要一個配置類,透過Enable模組驅動註解@EnableAsync 來開啟非同步功能。
實現非同步呼叫
第一步:新建配置類,開啟@Async功能支援
使用
@EnableAsync
來開啟非同步任務支援,
@EnableAsync
註解可以直接放在SpringBoot啟動類上,也可以單獨放在其他配置類上。我們這裡選擇使用單獨的配置類
SyncConfiguration
。
@Configuration @EnableAsync public class AsyncConfiguration { }
第二步:在方法上標記非同步呼叫
增加一個Component類,用來進行業務處理,同時新增@Async註解,代表該方法為非同步處理。
@Component @Slf4j public class AsyncTask { @SneakyThrows @Async public void doTask1() { long t1 = System.currentTimeMillis(); Thread.sleep(2000); long t2 = System.currentTimeMillis(); log.info("task1 cost {} ms" , t2-t1); } @SneakyThrows @Async public void doTask2() { long t1 = System.currentTimeMillis(); Thread.sleep(3000); long t2 = System.currentTimeMillis(); log.info("task2 cost {} ms" , t2-t1); } }
第三步:在Controller中進行非同步方法呼叫
@RestController @RequestMapping("/async") @Slf4j public class AsyncController { @Autowired private AsyncTask asyncTask; @RequestMapping("/task") public void task() throws InterruptedException { long t1 = System.currentTimeMillis(); asyncTask.doTask1(); asyncTask.doTask2(); Thread.sleep(1000); long t2 = System.currentTimeMillis(); log.info("main cost {} ms", t2-t1); } }
透過訪問
檢視控制檯日誌:
2021-11-25 15:48:37 [http-nio-8080-exec-8] INFO com.jianzh5.blog.async.AsyncController:26 - main cost 1009 ms 2021-11-25 15:48:38 [task-1] INFO com.jianzh5.blog.async.AsyncTask:22 - task1 cost 2005 ms 2021-11-25 15:48:39 [task-2] INFO com.jianzh5.blog.async.AsyncTask:31 - task2 cost 3005 ms
透過日誌可以看到:主執行緒不需要等待非同步方法執行完成,減少了響應時間,提高了介面效能。
透過上面三步我們就可以在SpringBoot中歡樂的使用非同步方法來提高我們介面效能了,是不是很簡單?
不過,如果你在實際專案開發中真這樣寫了,肯定會被老鳥們無情嘲諷,就這?
因為上面的程式碼忽略了一個最大的問題,就是給@Async非同步框架自定義執行緒池。
為什麼要給@Async自定義執行緒池?
使用
@Async
註解,在預設情況下用的是
SimpleAsyncTaskExecutor執行緒池,該執行緒池不是真正意義上的執行緒池。
使用此執行緒池無法實現執行緒重用,每次呼叫都會新建一條執行緒。若系統中不斷的建立執行緒,最終會導致系統佔用記憶體過高,引發
OutOfMemoryError
錯誤,關鍵程式碼如下:
public void execute(Runnable task, long startTimeout) { Assert.notNull(task, "Runnable must not be null"); Runnable taskToUse = this.taskDecorator != null ? this.taskDecorator.decorate(task) : task; //判斷是否開啟限流,預設為否 if (this.isThrottleActive() && startTimeout > 0L) { //執行前置操作,進行限流 this.concurrencyThrottle.beforeAccess(); this.doExecute(new SimpleAsyncTaskExecutor.ConcurrencyThrottlingRunnable(taskToUse)); } else { //未限流的情況,執行執行緒任務 this.doExecute(taskToUse); } } protected void doExecute(Runnable task) { //不斷建立執行緒 Thread thread = this.threadFactory != null ? this.threadFactory.newThread(task) : this.createThread(task); thread.start(); } //建立執行緒 public Thread createThread(Runnable runnable) { //指定執行緒名,task-1,task-2... Thread thread = new Thread(this.getThreadGroup(), runnable, this.nextThreadName()); thread.setPriority(this.getThreadPriority()); thread.setDaemon(this.isDaemon()); return thread; }
我們也可以直接透過上面的控制檯日誌觀察,每次列印的執行緒名都是[task-1]、[task-2]、[task-3]、[task-4].....遞增的。
正因如此,所以我們在使用Spring中的@Async非同步框架時一定要自定義執行緒池,替代預設的
SimpleAsyncTaskExecutor
。
Spring提供了多種執行緒池: SimpleAsyncTaskExecutor:不是真的執行緒池,這個類不重用執行緒,每次呼叫都會建立一個新的執行緒。 SyncTaskExecutor:這個類沒有實現非同步呼叫,只是一個同步操作。只適用於不需要多執行緒的地 ConcurrentTaskExecutor:Executor的適配類,不推薦使用。如果ThreadPoolTaskExecutor不滿足要求時,才用考慮使用這個類 ThreadPoolTaskScheduler:可以使用cron表示式 ThreadPoolTaskExecutor :最常使用,推薦。其實質是對java.util.concurrent.ThreadPoolExecutor的包裝
為@Async實現一個自定義執行緒池
@Configuration @EnableAsync public class SyncConfiguration { @Bean(name = "asyncPoolTaskExecutor") public ThreadPoolTaskExecutor executor() { ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); //核心執行緒數 taskExecutor.setCorePoolSize(10); //執行緒池維護執行緒的最大數量,只有在緩衝佇列滿了之後才會申請超過核心執行緒數的執行緒 taskExecutor.setMaxPoolSize(100); //快取佇列 taskExecutor.setQueueCapacity(50); //許的空閒時間,當超過了核心執行緒出之外的執行緒在空閒時間到達之後會被銷燬 taskExecutor.setKeepAliveSeconds(200); //非同步方法內部執行緒名稱 taskExecutor.setThreadNamePrefix("async-"); /** * 當執行緒池的任務快取佇列已滿並且執行緒池中的執行緒數目達到maximumPoolSize,如果還有任務到來就會採取任務拒絕策略 * 通常有以下四種策略: * ThreadPoolExecutor.AbortPolicy:丟棄任務並丟擲RejectedExecutionException異常。 * ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不丟擲異常。 * ThreadPoolExecutor.DiscardOldestPolicy:丟棄佇列最前面的任務,然後重新嘗試執行任務(重複此過程) * ThreadPoolExecutor.CallerRunsPolicy:重試新增當前的任務,自動重複呼叫 execute() 方法,直到成功 */ taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); taskExecutor.initialize(); return taskExecutor; } }
配置自定義執行緒池以後我們就可以大膽的使用
@Async
提供的非同步處理能力了。
多個執行緒池處理
在現實的網際網路專案開發中,針對高併發的請求,一般的做法是高併發介面單獨執行緒池隔離處理。
假設現在2個高併發介面:一個是修改使用者資訊介面,重新整理使用者redis快取;一個是下訂單介面,傳送app push資訊。往往會根據介面特徵定義兩個執行緒池,這時候我們在使用
@Async
時就需要透過指定執行緒池名稱進行區分。
為@Async指定執行緒池名字
@SneakyThrows @Async("asyncPoolTaskExecutor") public void doTask1() { long t1 = System.currentTimeMillis(); Thread.sleep(2000); long t2 = System.currentTimeMillis(); log.info("task1 cost {} ms" , t2-t1); }
當系統存在多個執行緒池時,我們也可以配置一個預設執行緒池,對於非預設的非同步任務再透過
@Async("otherTaskExecutor")
來指定執行緒池名稱。
配置預設執行緒池
可以修改配置類讓其實現AsyncConfigurer,並重寫getAsyncExecutor()方法,指定預設執行緒池:
@Configuration @EnableAsync @Slf4j public class AsyncConfiguration implements AsyncConfigurer { @Bean(name = "asyncPoolTaskExecutor") public ThreadPoolTaskExecutor executor() { ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); //核心執行緒數 taskExecutor.setCorePoolSize(2); //執行緒池維護執行緒的最大數量,只有在緩衝佇列滿了之後才會申請超過核心執行緒數的執行緒 taskExecutor.setMaxPoolSize(10); //快取佇列 taskExecutor.setQueueCapacity(50); //許的空閒時間,當超過了核心執行緒出之外的執行緒在空閒時間到達之後會被銷燬 taskExecutor.setKeepAliveSeconds(200); //非同步方法內部執行緒名稱 taskExecutor.setThreadNamePrefix("async-"); /** * 當執行緒池的任務快取佇列已滿並且執行緒池中的執行緒數目達到maximumPoolSize,如果還有任務到來就會採取任務拒絕策略 * 通常有以下四種策略: * ThreadPoolExecutor.AbortPolicy:丟棄任務並丟擲RejectedExecutionException異常。 * ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不丟擲異常。 * ThreadPoolExecutor.DiscardOldestPolicy:丟棄佇列最前面的任務,然後重新嘗試執行任務(重複此過程) * ThreadPoolExecutor.CallerRunsPolicy:重試新增當前的任務,自動重複呼叫 execute() 方法,直到成功 */ taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); taskExecutor.initialize(); return taskExecutor; } /** * 指定預設執行緒池 */ @Override public Executor getAsyncExecutor() { return executor(); } @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return (ex, method, params) -> log.error("執行緒池執行任務傳送未知錯誤,執行方法:{}",method.getName(),ex); } }
如下,
doTask1()
方法使用預設使用執行緒池
asyncPoolTaskExecutor
,
doTask2()
使用執行緒池
otherTaskExecutor
,非常靈活。
@Async public void doTask1() { long t1 = System.currentTimeMillis(); Thread.sleep(2000); long t2 = System.currentTimeMillis(); log.info("task1 cost {} ms" , t2-t1); } @SneakyThrows @Async("otherTaskExecutor") public void doTask2() { long t1 = System.currentTimeMillis(); Thread.sleep(3000); long t2 = System.currentTimeMillis(); log.info("task2 cost {} ms" , t2-t1); }
小結
@Async
非同步方法在日常開發中經常會用到,大家好好掌握,爭取早日成為老鳥!!!
本文來自:JAVA日知錄
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70003733/viewspace-2845072/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- SpringBoot 如何進行限流?老鳥們都這麼玩的!Spring Boot
- SpringBoot 如何進行引數校驗,老鳥們都這麼玩的!Spring Boot
- SpringBoot整合RocketMQ,老鳥們都是這麼玩的!Spring BootMQ
- SpringBoot中如何使用ObjectMapper,老鳥們都是這樣玩的?Spring BootObjectAPP
- SpringBoot 如何統一後端返回格式?老鳥們都是這樣玩的!Spring Boot後端
- Springboot 日誌、配置檔案、介面資料如何脫敏?老鳥們都是這樣玩的!Spring Boot
- Promise是如何實現非同步程式設計的?Promise非同步程式設計
- SpringBoot 非同步程式設計淺談Spring Boot非同步程式設計
- 什麼是非同步?5大非同步程式設計實現詳解!非同步程式設計
- python中非同步非阻塞如何實現Python非同步
- 很多程式設計師程式設計時都戴耳機?他們在聽什麼程式設計師
- 一個老程式設計師的程式設計之路,寫給年輕的程式設計師們程式設計師
- 程式設計25年後,現實將我打回菜鳥程式設計師的起點程式設計師
- [譯] 非同步程式設計:阻塞與非阻塞非同步程式設計
- 非同步程式設計在ArkTS中具體怎麼實現?非同步程式設計
- GPU程式設計(零):老黃和他的核彈們GPU程式設計
- 非同步程式設計方案----Promise實現小解非同步程式設計Promise
- 那些拼命加班的程式設計師們,後來都怎麼樣了?程式設計師
- 35歲以上的程式設計師們,後來都幹什麼去了?程式設計師
- 轉行程式設計師難麼?阿里巴巴五年老鳥:程式設計學不好的只有3種人行程程式設計師阿里
- 計算機學院的老師實際程式設計能力如何?計算機程式設計
- 設計模式也可以這麼簡單(7年開發老鳥PS註釋總結)設計模式
- 新手也能看懂的 SpringBoot 非同步程式設計指南Spring Boot非同步程式設計
- RedisSyncer同步引擎的設計與實現Redis
- 月薪20k的web前端開發程式設計師,他們都會的這6招Web前端程式設計師
- Java 網路程式設計 —— 實現非阻塞式的伺服器Java程式設計伺服器
- 程式設計入門,這763位老程式設計師有話講!程式設計師
- C++ concurrency::task實現非同步程式設計(WindowsC++非同步程式設計Windows
- 為什麼你們都這麼厲害。。。。
- 《JAVA併發程式設計實戰》原子變數和非阻塞同步機制Java程式設計變數
- 在Python中實現非同步程式設計,只需要這幾步就夠了Python非同步程式設計
- 程式設計師們,你們再這樣下去會沒朋友的程式設計師
- 程式設計師都幹些什麼?程式設計師
- 一文徹底搞定(阻塞/非阻塞/同步/非同步)網路IO、併發程式設計模型、非同步程式設計模型的愛恨情仇非同步程式設計模型
- 如何看出程式設計師是老碼農?這篇文章很正經程式設計師
- 你們不能這樣招聘程式設計師程式設計師
- SpringBoot引數非空校驗的非最優實現歷程Spring Boot
- PHP 這麼拉?長連線都搞不了?說說 PHP 的 socket 程式設計PHP程式設計