<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          新來(lái)了個(gè)技術(shù)總監(jiān):誰(shuí)再用 @Async 創(chuàng)建線程以后就不用來(lái)了!!

          共 3142字,需瀏覽 7分鐘

           ·

          2022-03-18 23:10

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

          原因無(wú)它,有小伙伴開(kāi)啟了線程池的暴力使用模式。沒(méi)錯(cuò),就是下面這篇文章。

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

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

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

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

          1.Spring的異步代碼

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

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

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

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

          @SpringBootApplication
          @EnableAsync
          public?class?DemoApplication?{

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

          @Component
          public?class?AsyncService?{
          ????@Async
          ????public?void?async(){
          ????????try?{
          ????????????Thread.sleep(1000);
          ????????????System.out.println(Thread.currentThread());
          ????????}catch?(Exception?ex){
          ????????????ex.printStackTrace();
          ????????}
          ????}
          }

          然后,做一個(gè)對(duì)應(yīng)的test接口,訪問(wèn)時(shí)會(huì)調(diào)用這個(gè)async方法。

          @ResponseBody
          @GetMapping("test")
          public?void?test(){
          ????service.async();
          }

          訪問(wèn)時(shí),直接打個(gè)斷點(diǎn),即可獲取執(zhí)行異步線程的線程池。

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

          throw?new?TaskRejectedException("Executor?["?+?executor?+?"]?did?not?accept?task:?"?+?task,?var4);

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

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

          2.是SpringBoot救了你

          SpringBoot是個(gè)好東西。

          在TaskExecutionAutoConfiguration中,通過(guò)生成ThreadPoolTaskExecutor的Bean,來(lái)提供默認(rèn)的Executor。

          @ConditionalOnMissingBean({Executor.class})
          public?ThreadPoolTaskExecutor?applicationTaskExecutor(TaskExecutorBuilder?builder)?
          {
          ????return?builder.build();
          }

          也就是我們上面所說(shuō)的那個(gè)。如果沒(méi)有SpringBoot的助力,Spring將默認(rèn)使用SimpleAsyncTaskExecutor。

          參見(jiàn)org.springframework.aop.interceptor.AsyncExecutionInterceptor。

          @Override
          @Nullable
          protected?Executor?getDefaultExecutor(@Nullable?BeanFactory?beanFactory)?{
          ?Executor?defaultExecutor?=?super.getDefaultExecutor(beanFactory);
          ?return?(defaultExecutor?!=?null???defaultExecutor?:?new?SimpleAsyncTaskExecutor());
          }

          這就是Spring大仙所干的事。

          SimpleAsyncTaskExecutor類設(shè)計(jì)的非常操蛋,因?yàn)樗繄?zhí)行一次,都會(huì)創(chuàng)建一個(gè)單獨(dú)的線程,根本沒(méi)有共用線程池。比如你的TPS是1000,異步執(zhí)行了任務(wù),那么你每秒將會(huì)生成1000個(gè)線程!

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

          protected?void?doExecute(Runnable?task)?{
          ?Thread?thread?=?(this.threadFactory?!=?null???this.threadFactory.newThread(task)?:?createThread(task));
          ?thread.start();
          }

          3.End

          明眼人一看,這種使用new線程的處理方式將會(huì)是非常可怕的。但就拿Spring本身來(lái)說(shuō),引用SimpleAsyncTaskExecutor這個(gè)類的地方還不少,包括比較流行的AsyncRestTemplate。

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

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

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

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

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

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

          程序汪資料鏈接

          程序汪接的7個(gè)私活都在這里,經(jīng)驗(yàn)整理

          Java項(xiàng)目分享 ?最新整理全集,找項(xiàng)目不累啦 07版

          堪稱神級(jí)的Spring Boot手冊(cè),從基礎(chǔ)入門到實(shí)戰(zhàn)進(jìn)階

          臥槽!字節(jié)跳動(dòng)《算法中文手冊(cè)》火了,完整版 PDF 開(kāi)放下載!

          臥槽!阿里大佬總結(jié)的《圖解Java》火了,完整版PDF開(kāi)放下載!

          字節(jié)跳動(dòng)總結(jié)的設(shè)計(jì)模式 PDF 火了,完整版開(kāi)放下載!


          歡迎添加程序汪個(gè)人微信 itwang009? 進(jìn)粉絲群或圍觀朋友圈

          瀏覽 44
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  青青草在线播放视频 | 中文字幕一区在线观看 | 亚洲中文字幕在线无码 | 三级日本三级网站三级网站在线 | 五月天色导航 |