0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

創(chuàng)建線程有那么難么?創(chuàng)建線程的大坑要避開

jf_ro2CN3Fa ? 來源:芋道源碼 ? 作者:芋道源碼 ? 2022-10-19 10:36 ? 次閱讀

1.Spring的異步代碼

2.是SpringBoot救了你

3.End

e59430cc-4c39-11ed-a3b6-dac502259ad0.jpg

在很久很久之前,我有一段痛苦的記憶。那種被故障所驅(qū)使的感覺,在我腦海里久久無法驅(qū)散。

原因無它,有小伙伴開啟了線程池的暴力使用模式。沒錯,就是下面這篇文章。

奪命故障 ! 炸出了投資人!

我有必要簡單的復(fù)述一下。其主要原因,就是開發(fā)人員,在每一次方法調(diào)用里,都創(chuàng)建了一個單獨的線程池去處理。這樣的話,如果請求量一增加,整個操作系統(tǒng)的壓力就會耗盡,最終所有的業(yè)務(wù)都無法響應(yīng)。

e5a5c77e-4c39-11ed-a3b6-dac502259ad0.png

我一直認為這是一個非常偶發(fā)的低級錯誤,發(fā)生頻率非常的低。但隨著這樣的故障越來越多,xjjdog認識到這是一個普遍的現(xiàn)象。

以異步性能優(yōu)化為目的,反而帶來的整體業(yè)務(wù)不可用的結(jié)果,是非常打臉的一種優(yōu)化。

1.Spring的異步代碼

Spring作為Java屆的杠把子框架,其過度封裝的API深得開發(fā)人員的喜愛。根據(jù)語義化編程的邏輯,只要某些關(guān)鍵字在語言層面上過得去,我們就可以把它給加上去。比如@Async注解。

我永遠想不通是什么給了開發(fā)人員勇氣,去加上這個@Async注解,因為這種涉及到多線程的東西,即使是自己去創(chuàng)建線程,也是心懷敬畏,唯恐擾了操作系統(tǒng)的安寧。@Async這樣的黑盒,真的可以那么順暢的使用么?

我們不妨debug一下代碼,讓子彈飛一會兒。

首先,生成一個小小的項目,然后在主類上加上必須的注解。嗯,別忘了這一環(huán),否則你后面加的注解將沒什么用處。

@SpringBootApplication
@EnableAsync
publicclassDemoApplication{

創(chuàng)造一個帶@Async注解的方法。

@Component
publicclassAsyncService{
@Async
publicvoidasync(){
try{
Thread.sleep(1000);
System.out.println(Thread.currentThread());
}catch(Exceptionex){
ex.printStackTrace();
}
}
}

然后,做一個對應(yīng)的test接口,訪問時會調(diào)用這個async方法。

@ResponseBody
@GetMapping("test")
publicvoidtest(){
service.async();
}

訪問時,直接打個斷點,即可獲取執(zhí)行異步線程的線程池。

e5b246ca-4c39-11ed-a3b6-dac502259ad0.png

可以看到,異步任務(wù)使用了一個線程池,它的corePoolSize=8, 阻塞隊列采用了無界隊列LinkedBlockingQueue。一旦采用了這樣的組合,最大線程數(shù)就會形同虛設(shè),因為超出8個線程的任務(wù),將全部會被放到無界隊列里。使得下面的代碼變成了擺設(shè)。

thrownewTaskRejectedException("Executor["+executor+"]didnotaccepttask:"+task,var4);

如果你的訪問量非常大,這些任務(wù)將全部堆積在LinkedBlockingQueue里。情況好一點的,這些任務(wù)的執(zhí)行會變得延遲很大;情況壞一點的,任務(wù)太多將直接造成內(nèi)存溢出OOM!

你可能會說,我可以自己指定另外一個ThreadPoolExceute,然后使用@Async注解來聲明啊。說這話的同學(xué),一定是能力比較強,或者Review的代碼比較少,沒有經(jīng)過豬隊友的洗禮。

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類設(shè)計的非常操蛋,因為它每執(zhí)行一次,都會創(chuàng)建一個單獨的線程,根本沒有共用線程池。比如你的TPS是1000,異步執(zhí)行了任務(wù),那么你每秒將會生成1000個線程!

這明顯是想要累死操作系統(tǒng)的節(jié)奏。

protectedvoiddoExecute(Runnabletask){
Threadthread=(this.threadFactory!=null?this.threadFactory.newThread(task):createThread(task));
thread.start();
}

3.End

明眼人一看,這種使用new線程的處理方式將會是非??膳碌?。但就拿Spring本身來說,引用SimpleAsyncTaskExecutor這個類的地方還不少,包括比較流行的AsyncRestTemplate。

e5c56796-4c39-11ed-a3b6-dac502259ad0.png

這暴露了很多風(fēng)險,尤其是竟然在這些列表中看到了redis的身影。這個類的設(shè)計,使得任務(wù)的執(zhí)行變的非常的不可控。

看這個API,我感覺Spring是進入了設(shè)計的魔怔狀態(tài)。

這個東西的隱藏bug可能還會更深!比如org.springframework.context.event.EventListener注解,用于實現(xiàn)DDD那套所謂的事件驅(qū)動模式,有不少框架直接set了SimpleAsyncTaskExecutor,那么就等死吧。

趕緊把SimpleAsyncTaskExecutor加入你的API黑名單,或者埋坑清單吧!

創(chuàng)建線程有那么難么?需要使用Spring創(chuàng)建的線程?有時候我實在是想不通,暴露出這樣的接口目的是為了什么。

就連原生的線程池我們還沒搞明白呢,你還給包了一層,這是方便我們甩鍋啊!

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • JAVA
    +關(guān)注

    關(guān)注

    19

    文章

    2948

    瀏覽量

    104375
  • API
    API
    +關(guān)注

    關(guān)注

    2

    文章

    1468

    瀏覽量

    61692
  • 代碼
    +關(guān)注

    關(guān)注

    30

    文章

    4700

    瀏覽量

    68107
  • spring
    +關(guān)注

    關(guān)注

    0

    文章

    335

    瀏覽量

    14278
  • 線程
    +關(guān)注

    關(guān)注

    0

    文章

    502

    瀏覽量

    19620

原文標題:新來了個技術(shù)總監(jiān):誰再用 @Async 創(chuàng)建線程以后就不用來了?。?/p>

文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    跨平臺的線程池組件--TP組件

    /銷毀代價是很高的。那么我們怎么去設(shè)計多線程編程呢???答案:對于長駐的線程,我們可以創(chuàng)建獨立的線程
    的頭像 發(fā)表于 04-06 15:39 ?821次閱讀

    fx3線程創(chuàng)建

    你好,如果我使用堆棧大小0x400(1024)使用Cyu3pthRead CueRead()API創(chuàng)建線程,那么我可以使用Cyu3PDMACHNEL CURATE()API創(chuàng)建多少個通
    發(fā)表于 10-16 07:52

    如何在psoc cy8kit-059中創(chuàng)建線程?

    你好,我想在PSoC CY8KIT-059中創(chuàng)建一個線程。我的Linux使用pthread創(chuàng)建一個線程但我不怎么在PSoC創(chuàng)建
    發(fā)表于 11-05 08:54

    初學(xué)RT-thread線程動態(tài)創(chuàng)建

    RT-thread初學(xué)線程動態(tài)創(chuàng)建線程靜態(tài)創(chuàng)建線程鉤子函數(shù)定時器獲取系統(tǒng)時間動態(tài)創(chuàng)建定時器靜態(tài)
    發(fā)表于 02-24 07:32

    python創(chuàng)建線程的兩種方法

    ()可以看到輸出hello Pythonhello MINGhello Pythonhello MING2. 用類創(chuàng)建線程相比較函數(shù)而言,使用類創(chuàng)建線程,會比較麻煩一點。首先,我們
    發(fā)表于 03-15 16:47

    請問大佬rt-thread smart用戶程序可以創(chuàng)建實時線程?

    RT,rt-smart用戶程序可以創(chuàng)建實時線程?現(xiàn)有例子看,感覺smart得內(nèi)核是實時得,用戶層程序是非實時得呢?如果用戶可以創(chuàng)建實時線程
    發(fā)表于 04-15 09:54

    在RT-Thread系統(tǒng)中創(chuàng)建線程哪幾種方式

    概述創(chuàng)建線程三要素:1.線程棧2.線程控制塊3.線程主體函數(shù)在RTT中線程
    發(fā)表于 05-07 14:14

    RTThread創(chuàng)建線程時??臻g是如何進行分配的

    RTThread在動態(tài)創(chuàng)建線程時,其中一個參數(shù)是分配的線程堆棧大小。單個線程的堆棧大小可以在rtconfig文件中定義,那么總的堆??臻g是由
    發(fā)表于 09-21 10:57

    怎么解決創(chuàng)建多個線程和消息隊列失敗的問題呢?

    我使用的是STM32L010RB,在Main函數(shù)中創(chuàng)建了4個線程,線程棧大小分別為256\\\\1024\\\\1024\\\\1024,結(jié)果在后面的線程中提示
    發(fā)表于 05-05 14:20

    linux下多線程創(chuàng)建與等待詳解

    帶來的問題回避進程嚴重得多。如果你的主線程,也就是main函數(shù)執(zhí)行的那個線程,在你其他線程推出之前就已經(jīng)退出,那么帶來的bug則不可估量。
    發(fā)表于 04-02 14:48 ?293次閱讀

    python創(chuàng)建線程的兩種方法

    1. 用函數(shù)創(chuàng)建線程 在Python3中,Python提供了一個內(nèi)置模塊 threading.Thread ,可以很方便地讓我們創(chuàng)建線程。 threading.Thread() 一
    的頭像 發(fā)表于 03-15 16:47 ?5244次閱讀

    python創(chuàng)建線程池的兩種方法

    在使用多線程處理任務(wù)時也不是線程越多越好,由于在切換線程的時候,需要切換上下文環(huán)境,依然會造成cpu的大量開銷。為解決這個問題,線程池的概念被提出來了。預(yù)先
    的頭像 發(fā)表于 03-16 16:15 ?5917次閱讀

    stm32裸機RT—thread開始創(chuàng)建線程詳解

    線程系統(tǒng)中,每個線程都是獨立的,互不干擾的,所以要為每個線程都分配獨立的??臻g,這個棧空間通常是一個預(yù)先定義好的全局數(shù)組,也可以是動態(tài)分配的一段內(nèi)存空間,但它們都存在于RAM中。 ? ? ? ?
    的頭像 發(fā)表于 05-19 15:02 ?4778次閱讀
    stm32裸機RT—thread開始<b class='flag-5'>創(chuàng)建</b><b class='flag-5'>線程</b>詳解

    linux內(nèi)核線程就這樣誕生了?

    線程是操作系統(tǒng)的重要組成部件之一,linux內(nèi)核中,內(nèi)核線程是如何創(chuàng)建的,在內(nèi)核啟動過程中,誕生了哪些支撐整個系統(tǒng)運轉(zhuǎn)的線程,本文將帶著這個疑問瞅一瞅內(nèi)核源碼,分析內(nèi)核
    的頭像 發(fā)表于 07-10 10:45 ?716次閱讀
    linux內(nèi)核<b class='flag-5'>線程</b>就這樣誕生了<b class='flag-5'>么</b>?

    線程池的創(chuàng)建方式幾種

    的開銷。線程池的創(chuàng)建方式多種,下面將詳細介紹幾種常用的線程創(chuàng)建方式。 手動創(chuàng)建
    的頭像 發(fā)表于 12-04 16:52 ?759次閱讀