<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      面試官:線程池如何按照core、max、queue的執行循序去執行?(內附詳細解析)

      前言

      這是一個真實的面試題。

      前幾天一個朋友在群里分享了他剛剛面試候選者時問的問題:"線程池如何按照core、max、queue的執行循序去執行?"

      我們都知道線程池中代碼執行順序是:corePool->workQueue->maxPool,源碼我都看過,你現在問題讓我改源碼??

      一時間群里炸開了鍋,小伙伴們紛紛打聽他所在的公司,然后拉黑避坑。(手動狗頭,大家一起調侃?(?????)?)

      關于線程池他一共問了這么幾個問題:

      • 線程池如何按照core、max、queue的順序去執行?
      • 子線程拋出的異常,主線程能感知到么?
      • 線程池發生了異常改怎樣處理?

      全是一些有意思的問題,我之前也寫過一篇很詳細的圖文教程:【萬字圖文-原創】 | 學會Java中的線程池,這一篇也許就夠了! ,不了解的小伙伴可以再回顧下~

      但是針對這幾個問題,可能大家一時間也有點懵。今天的文章我們以源碼為基礎來分析下該如何回答這三個問題。(之前沒閱讀過源碼也沒關系,所有的分析都會貼出源碼及圖解)

      線程池如何按照core、max、queue的順序執行?

      問題思考

      對于這個問題,很多小伙伴肯定會疑惑:"別人源碼中寫好的執行流程你為啥要改?這面試官腦子有病吧......"

      這里來思考一下現實工作場景中是否有這種需求?之前也看到過一份簡歷也寫到過這個問題:

      場景描述.png

      一個線程池執行的任務屬于IO密集型,CPU大多屬于閑置狀態,系統資源未充分利用。如果一瞬間來了大量請求,如果線程池數量大于coreSize時,多余的請求都會放入到等待隊列中。等待著corePool中的線程執行完成后再來執行等待隊列中的任務。

      試想一下,這種場景我們該如何優化?

      我們可以修改線程池的執行順序為corePool->maxPool->workQueue。 這樣就能夠充分利用CPU資源,提交的任務會被優先執行。當線程池中線程數量大于maxSize時才會將任務放入等待隊列中。

      你就說巧不巧?面試官的這個問題顯然是經過認真思考來提問的,這是一個很有意思的溫恩提,下面就一起看看如何解決吧。

      線程池運行流程

      我們都知道線程池執行流程是先corePoolworkQueue,最后才是maxPool的一個執行流程。

      執行流程.png

      線程池核心參數

      在回顧下ThreadPoolExecutor.execute()源碼前我們先回顧下線程池中的幾個重要參數:

      線程池核心參數.png

      我們來看下這幾個參數的定義:
      corePoolSize: 線程池中核心線程數量
      maximumPoolSize: 線程池中最大線程數量
      keepAliveTime: 非核心的空閑線程等待新任務的時間
      unit: 時間單位。配合allowCoreThreadTimeOut也會清理核心線程池中的線程。
      workQueue: 基于Blocking的任務隊列,最好選用有界隊列,指定隊列長度
      threadFactory: 線程工廠,最好自定義線程工廠,可以自定義每個線程的名稱
      handler: 拒絕策略,默認是AbortPolicy

      ThreadPoolExecutor.execute()源碼分析

      我們可以看下execute()如下:

      execute執行源碼.png

      接著來分析下執行過程:

      1. 第一步:workerCountOf(c)時間計算當前線程池中線程的個數,當線程個數小于核心線程數
      2. 第二步:線程池線程數量大于核心線程數,此時提交的任務會放入workQueue中,使用offer()進行操作
      3. 第三步:workQueue.offer()執行失敗,新提交的任務會直接執行,addWorker()會判斷如果當前線程池數量大于最大線程數,則執行拒絕策略

      好了,到了這里我們都已經很清楚了,關鍵在于第二步和第三步如何交換順序執行呢?

      解決思路

      仔細想一想,如果修改workQueue.offer()的實現不就可以達到目的了?我們先來畫圖來看一下:

      問題思路.png

      現在的問題就在于,如果當前線程池中coreSize < workCount < maxSize時,一定會先執行offer()操作。

      我們如果修改offer的實現是否可以完成執行順序的更換呢?這里也是畫圖來展示一下:

      解決方式.png

      Dubbo中EagerThreadPool解決方案

      湊巧Dubbo中也有類似的實現,在DubboEagerThreadPool自定義了一個BlockingQueue,在offer()方法中,如果當前線程池數量小于最大線程池時,直接返回false,這里就達到了調節線程池執行順序的目的。

      dubbo中解決方案.png

      源碼直達https://github.com/apache/dubbo/blob/master/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/eager/TaskQueue.java

      看到這里一切都真相大白了,解決思路以及方案都很簡單,學會了沒有?

      這個問題背后還隱藏了一些場景的優化、源碼的擴展等等知識,果然是一個值得思考的好問題。

      子線程拋出的異常,主線程能感知到么?

      問題思考

      這個問題其實也很容易回答,也僅僅是一個面試題而已,實際工作中子線程的異常不應該由主線程來捕獲。

      針對這個問題,希望大家清楚的是: 我們要明確線程代碼的邊界,異步化過程中,子線程拋出的異常應該由子線程自己去處理,而不是需要主線程感知來協助處理。

      解決方案

      解決方案很簡單,在虛擬機中,當一個線程如果沒有顯式處理異常而拋出時會將該異常事件報告給該線程對象的 java.lang.Thread.UncaughtExceptionHandler 進行處理,如果線程沒有設置 UncaughtExceptionHandler,則默認會把異常棧信息輸出到終端而使程序直接崩潰。

      所以如果我們想在線程意外崩潰時做一些處理就可以通過實現 UncaughtExceptionHandler 來滿足需求。

      我們使用線程池設置ThreadFactory時可以指定UncaughtExceptionHandler,這樣就可以捕獲到子線程拋出的異常了。

      代碼示例

      具體代碼如下:

      /**
       * 測試子線程異常問題
       *
       * @author wangmeng
       * @date 2020/6/13 18:08
       */
      public class ThreadPoolExceptionTest {
      
          public static void main(String[] args) throws InterruptedException {
              MyHandler myHandler = new MyHandler();
              ExecutorService execute = new ThreadPoolExecutor(10, 10,
                      0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), new ThreadFactoryBuilder().setUncaughtExceptionHandler(myHandler).build());
      
              TimeUnit.SECONDS.sleep(5);
              for (int i = 0; i < 10; i++) {
                  execute.execute(new MyRunner());
              }
          }
      
      
          private static class MyRunner implements Runnable {
              @Override
              public void run() {
                  int count = 0;
                  while (true) {
                      count++;
                      System.out.println("我要開始生產Bug了============");
                      if (count == 10) {
                          System.out.println(1 / 0);
                      }
      
                      if (count == 20) {
                          System.out.println("這里是不會執行到的==========");
                          break;
                      }
                  }
              }
          }
      }
      
      class MyHandler implements Thread.UncaughtExceptionHandler {
          private final static Logger LOGGER = LoggerFactory.getLogger(MyHandler.class);
          @Override
          public void uncaughtException(Thread t, Throwable e) {
              LOGGER.error("threadId = {}, threadName = {}, ex = {}", t.getId(), t.getName(), e.getMessage());
          }
      }
      

      執行結果:
      執行結果.png

      UncaughtExceptionHandler 解析

      我們來看下Thread中的內部接口UncaughtExceptionHandler

      public class Thread {
          ......
          /**
           * 當一個線程因未捕獲的異常而即將終止時虛擬機將使用 Thread.getUncaughtExceptionHandler()
           * 獲取已經設置的 UncaughtExceptionHandler 實例,并通過調用其 uncaughtException(...) 方
           * 法而傳遞相關異常信息。
           * 如果一個線程沒有明確設置其 UncaughtExceptionHandler,則將其 ThreadGroup 對象作為其
           * handler,如果 ThreadGroup 對象對異常沒有什么特殊的要求,則 ThreadGroup 會將調用轉發給
           * 默認的未捕獲異常處理器(即 Thread 類中定義的靜態未捕獲異常處理器對象)。
           *
           * @see #setDefaultUncaughtExceptionHandler
           * @see #setUncaughtExceptionHandler
           * @see ThreadGroup#uncaughtException
           */
          @FunctionalInterface
          public interface UncaughtExceptionHandler {
              /**
               * 未捕獲異常崩潰時回調此方法
               */
              void uncaughtException(Thread t, Throwable e);
          }
      
          /**
           * 靜態方法,用于設置一個默認的全局異常處理器。
           */
          public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
               defaultUncaughtExceptionHandler = eh;
           }
      
          /**
           * 針對某個 Thread 對象的方法,用于對特定的線程進行未捕獲的異常處理。
           */
          public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
              checkAccess();
              uncaughtExceptionHandler = eh;
          }
      
          /**
           * 當 Thread 崩潰時會調用該方法獲取當前線程的 handler,獲取不到就會調用 group(handler 類型)。
           * group 是 Thread 類的 ThreadGroup 類型屬性,在 Thread 構造中實例化。
           */
          public UncaughtExceptionHandler getUncaughtExceptionHandler() {
              return uncaughtExceptionHandler != null ?
                  uncaughtExceptionHandler : group;
          }
      
          /**
           * 線程全局默認 handler。
           */
          public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler() {
              return defaultUncaughtExceptionHandler;
          }
          ......
      }
      

      部分內容參考自:https://mp.weixin.qq.com/s/ghnNQnpou6-NemhFjpl4Jg

      線程池發生了異常改怎樣處理?

      線程池中線程運行過程中出現了異常該怎樣處理呢?線程池提交任務有兩種方式,分別是execute()submit(),這里會依次說明。

      ThreadPoolExecutor.runWorker()實現

      不管是使用execute()還是submit()提交任務,最終都會執行到ThreadPoolExecutor.runWorker(),我們來看下源碼(源碼基于JDK1.8):

      runWorker().png

      我們看到在執行task.run()時,出現異常會直接向上拋出,這里處理的最好的方式就是在我們業務代碼中使用try...catch()來捕獲異常。

      FutureTask.run()實現

      如果我們使用submit()來提交任務,在ThreadPoolExecutor.runWorker()方法執行時最終會調用到FutureTask.run()方法里面去,不清楚的小伙伴也可以看下我之前的文章:

      線程池續:你必須要知道的線程池submit()實現原理之FutureTask!

      FutureTask.run().png

      這里可以看到,如果業務代碼拋出異常后,會被catch捕獲到,然后調用setExeception()方法:

      FutureTask.setException().png

      可以看到其實類似于直接吞掉了,當我們調用get()方法的時候異常信息會包裝到FutureTask內部的變量outcome中,我們也會獲取到對應的異常信息。

      ThreadPoolExecutor.runWorker()最后finally中有一個afterExecute()鉤子方法,如果我們重寫了afterExecute()方法,就可以獲取到子線程拋出的具體異常信息Throwable了。

      結論

      對于線程池、包括線程的異常處理推薦以下方式:

      1. 直接使用try/catch,這個也是最推薦的方式
      2. 在我們構造線程池的時候,重寫uncaughtException()方法,上面示例代碼也有提到:
      public class ThreadPoolExceptionTest {
      
          public static void main(String[] args) throws InterruptedException {
              MyHandler myHandler = new MyHandler();
              ExecutorService execute = new ThreadPoolExecutor(10, 10,
                      0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), new ThreadFactoryBuilder().setUncaughtExceptionHandler(myHandler).build());
      
              TimeUnit.SECONDS.sleep(5);
              for (int i = 0; i < 10; i++) {
                  execute.execute(new MyRunner());
              }
          }
      }
      
      class MyHandler implements Thread.UncaughtExceptionHandler {
          private final static Logger LOGGER = LoggerFactory.getLogger(MyHandler.class);
          @Override
          public void uncaughtException(Thread t, Throwable e) {
              LOGGER.error("threadId = {}, threadName = {}, ex = {}", t.getId(), t.getName(), e.getMessage());
          }
      }
      

      3 直接重寫afterExecute()方法,感知異常細節

      總結

      這篇文章到這里就結束了,不知道小伙伴們有沒有一些感悟或收獲?

      通過這幾個面試問題,我也深刻的感受到學習知識要多思考,看源碼的過程中要多設置一些場景,這樣才會收獲更多。

      原創干貨分享.png

      posted @ 2020-06-15 07:27  一枝花算不算浪漫  閱讀(2286)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 四虎影视www在线播放| 无码天堂亚洲国产av麻豆| 亚洲精品一区二区三区四区乱码| 久章草在线毛片视频播放| 亚洲精品成人7777在线观看| 无码国产偷倩在线播放| 黄色特级片一区二区三区| 最新国产精品亚洲| 最新亚洲人成网站在线观看| 国产播放91色在线观看| 92国产精品午夜福利免费| 久久精品国产亚洲精品色婷婷| 色欲av亚洲一区无码少妇| 毛片亚洲AV无码精品国产午夜| 亚洲丶国产丶欧美一区二区三区| 一本色道久久综合无码人妻| 一区二区三区岛国av毛片| 亚洲 一区二区 在线| 国产尤物精品自在拍视频首页| 免费视频爱爱太爽了| 久久中文字幕无码一区二区| 亚洲深深色噜噜狠狠网站| 亚洲va韩国va欧美va| 成人亚洲一区二区三区在线| 无码人妻aⅴ一区二区三区蜜桃| 国产情侣激情在线对白| 国产精品视频一区二区三区无码| 日本亚洲欧洲无免费码在线| 国产中文字幕在线一区| 绵竹市| 精品一日韩美女性夜视频| 亚洲欧美日韩愉拍自拍美利坚| 国产免费踩踏调教视频| 亚洲熟妇熟女久久精品综合 | 日本一高清二区视频久二区| 色一乱一伦一图一区二区精品| 国产精品美女久久久久久麻豆| 1区2区3区4区产品不卡码网站| 國产AV天堂| 国产AV影片麻豆精品传媒| 国内不卡的一区二区三区|