1.Spring的異步代碼
2.是SpringBoot救了你
3.End
在很久很久之前,我有一段痛苦的記憶。那種被故障所驅使的感覺,在我腦海里久久無法驅散。
原因無它,有小伙伴開啟了線程池的暴力使用模式。沒錯,就是下面這篇文章。
奪命故障 ! 炸出了投資人!
我有必要簡單的復述一下。其主要原因,就是開發人員,在每一次方法調用里,都創建了一個單獨的線程池去處理。這樣的話,如果請求量一增加,整個操作系統的壓力就會耗盡,最終所有的業務都無法響應。
我一直認為這是一個非常偶發的低級錯誤,發生頻率非常的低。但隨著這樣的故障越來越多,xjjdog認識到這是一個普遍的現象。
以異步性能優化為目的,反而帶來的整體業務不可用的結果,是非常打臉的一種優化。
1.Spring的異步代碼
Spring作為Java屆的杠把子框架,其過度封裝的API深得開發人員的喜愛。根據語義化編程的邏輯,只要某些關鍵字在語言層面上過得去,我們就可以把它給加上去。比如@Async注解。
我永遠想不通是什么給了開發人員勇氣,去加上這個@Async注解,因為這種涉及到多線程的東西,即使是自己去創建線程,也是心懷敬畏,唯恐擾了操作系統的安寧。@Async這樣的黑盒,真的可以那么順暢的使用么?
我們不妨debug一下代碼,讓子彈飛一會兒。
首先,生成一個小小的項目,然后在主類上加上必須的注解。嗯,別忘了這一環,否則你后面加的注解將沒什么用處。
@SpringBootApplication @EnableAsync publicclassDemoApplication{
創造一個帶@Async注解的方法。
@Component publicclassAsyncService{ @Async publicvoidasync(){ try{ Thread.sleep(1000); System.out.println(Thread.currentThread()); }catch(Exceptionex){ ex.printStackTrace(); } } }
然后,做一個對應的test接口,訪問時會調用這個async方法。
@ResponseBody @GetMapping("test") publicvoidtest(){ service.async(); }
訪問時,直接打個斷點,即可獲取執行異步線程的線程池。
可以看到,異步任務使用了一個線程池,它的corePoolSize=8, 阻塞隊列采用了無界隊列LinkedBlockingQueue。一旦采用了這樣的組合,最大線程數就會形同虛設,因為超出8個線程的任務,將全部會被放到無界隊列里。使得下面的代碼變成了擺設。
thrownewTaskRejectedException("Executor["+executor+"]didnotaccepttask:"+task,var4);
如果你的訪問量非常大,這些任務將全部堆積在LinkedBlockingQueue里。情況好一點的,這些任務的執行會變得延遲很大;情況壞一點的,任務太多將直接造成內存溢出OOM!
你可能會說,我可以自己指定另外一個ThreadPoolExceute,然后使用@Async注解來聲明啊。說這話的同學,一定是能力比較強,或者Review的代碼比較少,沒有經過豬隊友的洗禮。
2.是SpringBoot救了你
SpringBoot是個好東西。
在TaskExecutionAutoConfiguration中,通過生成ThreadPoolTaskExecutor的Bean,來提供默認的Executor。
@ConditionalOnMissingBean({Executor.class}) publicThreadPoolTaskExecutorapplicationTaskExecutor(TaskExecutorBuilderbuilder){ returnbuilder.build(); }
也就是我們上面所說的那個。如果沒有SpringBoot的助力,Spring將默認使用SimpleAsyncTaskExecutor。
參見org.springframework.aop.interceptor.AsyncExecutionInterceptor。
@Override @Nullable protectedExecutorgetDefaultExecutor(@NullableBeanFactorybeanFactory){ ExecutordefaultExecutor=super.getDefaultExecutor(beanFactory); return(defaultExecutor!=null?defaultExecutor:newSimpleAsyncTaskExecutor()); }
這就是Spring大仙所干的事。
SimpleAsyncTaskExecutor類設計的非常操蛋,因為它每執行一次,都會創建一個單獨的線程,根本沒有共用線程池。比如你的TPS是1000,異步執行了任務,那么你每秒將會生成1000個線程!
這明顯是想要累死操作系統的節奏。
protectedvoiddoExecute(Runnabletask){ Threadthread=(this.threadFactory!=null?this.threadFactory.newThread(task):createThread(task)); thread.start(); }
3.End
明眼人一看,這種使用new線程的處理方式將會是非常可怕的。但就拿Spring本身來說,引用SimpleAsyncTaskExecutor這個類的地方還不少,包括比較流行的AsyncRestTemplate。
這暴露了很多風險,尤其是竟然在這些列表中看到了redis的身影。這個類的設計,使得任務的執行變的非常的不可控。
看這個API,我感覺Spring是進入了設計的魔怔狀態。
這個東西的隱藏bug可能還會更深!比如org.springframework.context.event.EventListener注解,用于實現DDD那套所謂的事件驅動模式,有不少框架直接set了SimpleAsyncTaskExecutor,那么就等死吧。
趕緊把SimpleAsyncTaskExecutor加入你的API黑名單,或者埋坑清單吧!
創建線程有那么難么?需要使用Spring創建的線程?有時候我實在是想不通,暴露出這樣的接口目的是為了什么。
就連原生的線程池我們還沒搞明白呢,你還給包了一層,這是方便我們甩鍋啊!
-
JAVA
+關注
關注
19文章
2959瀏覽量
104553 -
API
+關注
關注
2文章
1486瀏覽量
61819 -
代碼
+關注
關注
30文章
4750瀏覽量
68357 -
spring
+關注
關注
0文章
338瀏覽量
14311 -
線程
+關注
關注
0文章
504瀏覽量
19651
原文標題:新來了個技術總監:誰再用 @Async 創建線程以后就不用來了!!
文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論