<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>

          有些線程跑著跑著就不見了

          共 4925字,需瀏覽 10分鐘

           ·

          2021-01-21 23:35

          點擊藍色「小黑十一點半關注樓下小黑哥??

          點擊下方卡片可搜索文章??

          前言

          Hello,大家好,我是樓下小黑哥~

          最近接了一個業(yè)務需求,需求倒是不難,三下五除二就整理出設計方案,然后就開始代碼改造。

          啪,很快,就完成代碼改造,然后提測給測試小姐姐。

          小姐姐前面測試好好的,測到這個工程的時候,突然跟我反饋,你看這個這個工程跑著跑著就不動了,日志什么也沒了。

          那時候正在忙,想著我就沒改幾行代碼,也沒涉及核心邏輯,那肯定沒問題的。

          于是回復小姐姐,業(yè)務邏輯執(zhí)行的太慢了吧,再等個半小時再看看?

          一小時后,小姐姐又來找我,我都等了一小時,這個工程還是沒動啊,日志還是沒有啊。

          這下不能拖了,上去仔細一看,還真是,怎么就沒了呢?

          先簡單說下這段代碼,就是使用一個異步線程執(zhí)行一段業(yè)務邏輯,示例代碼如下:

          //?前置邏輯
          .....
          Thread?thread=new?Thread(new?Runnable()?{
          ????@Override
          ????public?void?run()?{
          ????????try?{
          ???????????//?異步線程執(zhí)行其他業(yè)務邏輯
          ????????}?catch?(Exception?e)?{
          ???????????//?不進行任何代碼處理
          ????????}
          ????}
          });
          thread.start();

          憑著老程序員的經(jīng)驗,猜到可能是異步線程內(nèi)發(fā)生了異常,導致異步線程退出,不再繼續(xù)執(zhí)行。而又因為上述代碼「吃掉」了異常,這就導致我們從外部看起來這個工程跑著跑著就不動了,日志什么也沒了。

          于是改造了一下,打印出相關異常日志,最終定位問題,原來是小姐姐造的數(shù)據(jù)存在問題,從而引發(fā) NPE 問題。


          「不知道大家有沒有碰到過上面的情況,使用線程異步執(zhí)行相關邏輯,但是執(zhí)行到一半突然就像卡主一般,不再繼續(xù)往下執(zhí)行。」

          小黑哥碰到過幾次,這幾次原因都不太相同,總結(jié)起來分為下面三種情況:

          • 異步任務長時間被阻塞

          • 異步任務發(fā)生異常

          • 異步任務異常被吃掉

          異步任務長時間被阻塞

          第一種,異步線程執(zhí)行任務,這個任務需要通過網(wǎng)絡調(diào)用其他遠端服務。假設服務端響應的非常慢,而我們設置的網(wǎng)絡超時時間又很長,這就會導致這個線程長時間被阻塞。

          假設異步任務偽碼如下:

          ThreadPoolExecutor?threadPool=?....;
          threadPool.execute(()?->?{
          //?1.調(diào)用遠端服務
          Socket?socket....;
          //?2.設置超時時間
          socket.setSoTimeout(60*1000);
          //?3.讀取服務端返回
          socket.read();
          });

          上面程序中,如果服務端一直沒有返回,那么異步線程將會一直被阻塞,直到超時。

          這種情況其實還好,我們無非等待一段時間,就可以看到異步線程繼續(xù)往下執(zhí)行任務。

          舉一個極端的例子,假設上面的代碼沒有設置超時時間,而服務端一直沒有返回響應,「此時異步線程就會被一直阻塞」

          除了上面網(wǎng)絡讀取阻塞的例子,常見情況還有

          • 執(zhí)行了長時間休眠,比如 TimeUnit.MINUTES.sleep(60)
          • 內(nèi)部發(fā)生了死鎖
          • 等等

          如果異步線程長時間被阻塞,而異步任務執(zhí)行又比較頻繁,那么線程池內(nèi)可用線程將會被慢慢耗盡,此時后續(xù)任務就會被拒絕執(zhí)行。

          解決辦法

          其實非常簡單,首先我們使用 jstack 命令 「dump」 一下當前 Java 應用的線程堆棧情況,然后根據(jù)線程池名字定位相關線程即可。

          網(wǎng)上隨便找了堆棧圖

          如果沒有自定義線程池 ThreadFactory 參數(shù),那查找定位被阻塞線程就比較麻煩了。

          所以創(chuàng)建線程池建議自定義 ThreadFactory 參數(shù),這對于后期排查問題非常有用。

          異步任務異常未捕獲

          上面的情況,異步線程其實還活著,只是被阻塞沒辦法執(zhí)行后續(xù)的邏輯。

          那這一類情況呢,與上面不太一樣,由于異步任務內(nèi)部發(fā)生錯誤,拋出異常,而代碼邏輯中又沒有進行捕獲處理,從而導致線程提前異常退出。

          異常退出偽碼如下:

          //?1.創(chuàng)建執(zhí)行的任務
          Runnable?runnable=new?Runnable()?{
          ????@Override
          ????public?void?run()?{
          ???????//?執(zhí)行前置邏輯
          ????????//?拋出異常
          ????????int?i=100/0;
          ???????//?執(zhí)行后置邏輯
          ????????
          ????}
          };
          //?2.創(chuàng)建線程
          Thread?thread=new?Thread(runnable);
          //?3.運行異步線程
          thread.start();
          //?其他業(yè)務邏輯

          上述代碼中,異步線程執(zhí)行到除零邏輯,將會拋出異常,然后異步線程將會異常退出。

          「異步線程內(nèi)拋出的異常日志僅僅只會被打印到控制臺,而不會被記錄到日志文件中?!?/strong>

          所以正常的業(yè)務日志中是見不到線程異常的日志,這就給了我們一種假象,異步線程看起來還在執(zhí)行任務,其實它已經(jīng)掛了。

          PS:上面的話可能不好理解,舉個例子,如果你使用 IDEA 執(zhí)行上面這段程序,異常日志將會被輸出到 IDEA 下方控制臺。

          而如果我們在 Linux 機器上執(zhí)行這段程序,異常日志僅僅只會顯示在當前終端窗口上,一旦關閉當前終端窗口,日志就沒。了。

          如果想要保存這種日志,我們需要將 stdout 重定向到日志文件中,比如執(zhí)行以下命令:

          --?將?stdout?重定向輸出到文件中
          nohup?java??xxxx?>?$STDOUT_FILE?2>&1?&

          解決辦法

          第一種解決辦法,其實很多讀者已經(jīng)想到了,異步線程內(nèi)使用 try..catch 語句捕獲所有異常即可。

          「沒錯,就是這么簡單。」

          不過這里提一點,一般我們使用 try..catch僅僅只會捕獲 Exception異常。

          那么極端情況下,異步線程內(nèi)如果拋出 Error,比如拋出了 java.lang.NoClassDefFoundError,此時是沒法捕獲,異步線程依舊會異常退出。

          所以我們可以使用try..catch捕獲 Throwable,這樣及時發(fā)生 Error錯誤,也會被捕獲。

          不過個人覺得捕獲Exception異常就夠了,正常工程應用很少會發(fā)生 Error錯誤,所以我們只要了解有這個可能即可。

          ps:之前同事上線一個應用,使用異步線程執(zhí)行任務,每次執(zhí)行到一半,都不再繼續(xù)執(zhí)行。

          由于異步線程內(nèi)使用try..catch捕獲處理了 Exception異常,所以找了半天不知道什么問題。

          最后,小黑哥排查 stdout 輸出日志,才發(fā)現(xiàn)異步線程發(fā)生 Error錯誤。

          這種解決本法需要我們主動去捕獲異常,而下面第二種解決辦法,設置線程異常處理方法。

          一旦設置完成,如果異步線程內(nèi)發(fā)生異常,線程退出之前將會調(diào)用異常處理方法。

          我們拿 Thread 來講,其設置方法如下:

          Runnable?runnable=new?Runnable()?{
          ????@Override
          ????public?void?run()?{
          ????????int?i=100/0;
          ????}
          };

          Thread?thread=new?Thread(runnable);
          thread.setUncaughtExceptionHandler(new?Thread.UncaughtExceptionHandler()?{
          ????@Override
          ????public?void?uncaughtException(Thread?t,?Throwable?e)?{
          ????????System.out.println(t.getName()+"發(fā)生異常"+e.getMessage());
          ????}
          });
          thread.start();

          不過生產(chǎn)環(huán)境不建議直接使用 Thread,我們需要使用線程池代替。

          線程池設置異常處理方法可以分為兩種,如果我們使用 ThreadPoolExecutor#execute執(zhí)行異步任務,那我們需要在自定義線程池的時候,使用 ThreadFactory 設置。

          ThreadPoolExecutor?threadPool?=new?ThreadPoolExecutor(
          ????????5,
          ????????10,
          ????????60,
          ????????TimeUnit.SECONDS,new?ArrayBlockingQueue<>(100),
          ??????//?這里使用?Guava?的?ThreadFactoryBuilder?類,方便構(gòu)造?ThreadFactory
          ????????new?ThreadFactoryBuilder().setUncaughtExceptionHandler(new?Thread.UncaughtExceptionHandler()?{
          ????????????@Override
          ????????????public?void?uncaughtException(Thread?t,?Throwable?e)?{
          ????????????????//?處理異常
          ????????????}
          ????????}).build()
          ????????);

          如果你當前使用 ThreadPoolExecutor#submit執(zhí)行異步任務,那就簡單了,我們可以直接通過 Future#get獲取到線程內(nèi)拋出的異常。

          Future?future?=?threadPool.submit(new?Callable()?{
          ????@Override
          ????public?Object?call()?throws?Exception?{
          ????????return?"小黑十一點半";
          ????}
          });

          try?{
          ????future.get();
          }?catch?(InterruptedException?e)?{
          ????e.printStackTrace();
          }?catch?(ExecutionException?e)?{
          ????//?線程內(nèi)拋出異常將會被封裝在?ExecutionException?內(nèi)
          }

          異步任務異常被吃掉

          好了,終于到最后一種情況了,小黑哥這次碰到就是這種??。

          這種情況具體來說就是異步線程內(nèi)使用 try..catch 語句捕獲了所有異常,但是沒有在 catch語句中進行任何代碼處理。

          Thread?thread=new?Thread(new?Runnable()?{
          ????@Override
          ????public?void?run()?{
          ????????try?{
          ????????????int?i=100/0;
          ????????}?catch?(Exception?e)?{
          ???????????//?不進行任何代碼處理
          ????????}
          ????}
          });
          thread.start();

          如上述代碼所示,catch語句中沒有進行任何代碼處理。即使異步線程內(nèi)真發(fā)生了異常,也不會有任何提示,這個異常就像被吃掉一般。

          總結(jié)

          多線程編程原本就比較復雜,我們需要處理各種問題,那今天主要介紹了一下其中的一個問題:

          「異步線程突然停止,就像卡主一般,不再繼續(xù)執(zhí)行代碼邏輯,沒有任何響應」

          那這類問題,小黑哥根據(jù)自己碰到情況,總結(jié)為三類:

          • 異步任務長時間被阻塞

          • 異步任務異常

          • 異步任務異常被吃掉。

          對于第一種,我們在網(wǎng)絡編程中及時設置超時時間,一般都能避免。

          對于第二、第三種情況,這就需要我們建立一個良好的編程習慣,使用try..catch 捕獲所有異常,并且 catch塊中一定做一些處理,比如說打印相關日志。

          好了,今天的文章就到這里,我是樓下小黑哥~

          「最近真的超忙,簡直忙炸了。這么忙了,還給大家輸出一篇干貨,點個贊,來個在看,不過分吧?」



          往期精彩回顧




          這個面試官不講套路,怎么上來就問個架構(gòu)問題|面經(jīng)分享
          一條失去條件的動態(tài) SQL,到手的年終獎飛了|文末彩蛋
          老大甩給我 30G 文件,讓小黑哥幾天內(nèi)全部導入到數(shù)據(jù)庫|文末送書



          瀏覽 66
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                    zzijzzij亚洲日本少妇ji… | 久草福利资源 | 国产精品1区2区 | 中文av中文字幕 中文一级久久黄色 | 日韩中文字幕在线免费视频 |