xylz,imxylz

          關注后端架構、中間件、分布式和并發編程

             :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::
            111 隨筆 :: 10 文章 :: 2680 評論 :: 0 Trackbacks

          Java中線程執行的任務接口java.lang.Runnable 要求不拋出Checked異常,

          public interface Runnable {

              
          public abstract void run();
          }

          那么如果 run() 方法中拋出了RuntimeException,將會怎么處理了?

          通常java.lang.Thread對象運行設置一個默認的異常處理方法:

          java.lang.Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler)

          而這個默認的靜態全局的異常捕獲方法時輸出堆棧。

          當然,我們可以覆蓋此默認實現,只需要一個自定義的java.lang.Thread.UncaughtExceptionHandler接口實現即可。

          public interface UncaughtExceptionHandler {

              
          void uncaughtException(Thread t, Throwable e);
          }

          而在線程池中卻比較特殊。默認情況下,線程池 java.util.concurrent.ThreadPoolExecutor 會Catch住所有異常, 當任務執行完成(java.util.concurrent.ExecutorService.submit(Callable))獲取其結果 時(java.util.concurrent.Future.get())會拋出此RuntimeException。

          /**
           * Waits if necessary for the computation to complete, and then
           * retrieves its result.
           *
           * 
          @return the computed result
           * 
          @throws CancellationException if the computation was cancelled
           * 
          @throws ExecutionException if the computation threw an exception
           * 
          @throws InterruptedException if the current thread was interrupted while waiting
           
          */
          V get() 
          throws InterruptedException, ExecutionException;

          其中 ExecutionException 異常即是java.lang.Runnable 或者 java.util.concurrent.Callable 拋出的異常。

          也就是說,線程池在執行任務時捕獲了所有異常,并將此異常加入結果中。這樣一來線程池中的所有線程都將無法捕獲到拋出的異常。 從而無法通過設置線程的默認捕獲方法攔截的錯誤異常。

          也不同通過自定義線程來完成異常的攔截。

          好在java.util.concurrent.ThreadPoolExecutor 預留了一個方法,運行在任務執行完畢進行擴展(當然也預留一個protected方法beforeExecute(Thread t, Runnable r)):

          protected void afterExecute(Runnable r, Throwable t) { } 

          此方法的默認實現為空,這樣我們就可以通過繼承或者覆蓋ThreadPoolExecutor 來達到自定義的錯誤處理。

          解決辦法如下:

          ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(111001, TimeUnit.MINUTES, //
                  new ArrayBlockingQueue<Runnable>(10000),//
                  new DefaultThreadFactory()) {

              
          protected void afterExecute(Runnable r, Throwable t) {
                  
          super.afterExecute(r, t);
                  printException(r, t);
              }
          };

          private static void printException(Runnable r, Throwable t) {
              
          if (t == null && r instanceof Future<?>) {
                  
          try {
                      Future
          <?> future = (Future<?>) r;
                      
          if (future.isDone())
                          future.get();
                  } 
          catch (CancellationException ce) {
                      t 
          = ce;
                  } 
          catch (ExecutionException ee) {
                      t 
          = ee.getCause();
                  } 
          catch (InterruptedException ie) {
                      Thread.currentThread().interrupt(); 
          // ignore/reset
                  }
              }
              
          if (t != null)
                  log.error(t.getMessage(), t);
          }

          此辦法的關鍵在于,事實上 afterExecute 并不會總是拋出異常 Throwable t,通過查看源碼得知,異常是封裝在此時的Future對象中的, 而此Future對象其實是一個java.util.concurrent.FutureTask的實現,默認的run方法其實調用的 java.util.concurrent.FutureTask.Sync.innerRun()。

          void innerRun() {
              if (!compareAndSetState(0, RUNNING))
                  return;
              try {
                  runner = Thread.currentThread();
                  if (getState() == RUNNING) // recheck after setting thread
                      innerSet(callable.call());
                  else
                      releaseShared(0); // cancel
              } catch (Throwable ex) {
                  innerSetException(ex);
              }
          }

          void innerSetException(Throwable t) {
              for (;;) {
                  int s = getState();
                  if (s == RAN)
                      return;
                  if (s == CANCELLED) {
                      // aggressively release to set runner to null,
                      
          // in case we are racing with a cancel request
                      
          // that will try to interrupt runner
                      releaseShared(0);
                      return;
                  }
                  if (compareAndSetState(s, RAN)) {
                      exception = t;
                      result = null;
                      releaseShared(0);
                      done();
                      return;
                  }
              }
          }

          這里我們可以看到它吃掉了異常,將異常存儲在java.util.concurrent.FutureTask.Sync的exception字段中:

          /** The exception to throw from get() */
          private Throwable exception;

          當我們獲取異步執行的結果時, java.util.concurrent.FutureTask.get()

          public V get() throws InterruptedException, ExecutionException {
              
          return sync.innerGet();
          }

          java.util.concurrent.FutureTask.Sync.innerGet()

          V innerGet() throws InterruptedException, ExecutionException {
              acquireSharedInterruptibly(
          0);
              
          if (getState() == CANCELLED)
                  
          throw new CancellationException();
              
          if (exception != null)
                  
          throw new ExecutionException(exception);
              
          return result;
          }

          異常就會被包裝成ExecutionException異常拋出。

          也就是說當我們想線程池 ThreadPoolExecutor(java.util.concurrent.ExecutorService)提交任務時, 如果不理會任務結果(Feture.get()),那么此異常將被線程池吃掉。

          <T> Future<T> submit(Callable<T> task);
          Future
          <?> submit(Runnable task);

          而java.util.concurrent.ScheduledThreadPoolExecutor是繼承ThreadPoolExecutor的,因此情況類似。

          結論,通過覆蓋ThreadPoolExecutor.afterExecute 方法,我們才能捕獲到任務的異常(RuntimeException)。

          原文地址:http://imxylz.com/blog/2013/08/02/handling-the-uncaught-exception-of-java-thread-pool/



          ©2009-2014 IMXYLZ |求賢若渴
          posted on 2013-08-05 16:45 imxylz 閱讀(29874) 評論(6)  編輯  收藏 所屬分類: Java Concurrency

          評論

          # re: 捕獲Java線程池執行任務拋出的異常 2013-08-08 10:34 陜西BOY
          V5的大濕重出江湖

          @Test
          public void testThread(){
          ThreadTest tt = new ThreadTest();
          tt.setUncaughtExceptionHandler(new LocalUncaughtExceptionHandler());
          tt.start();
          }  回復  更多評論
            

          # re: 捕獲Java線程池執行任務拋出的異常[未登錄] 2013-08-15 10:24 Ryan
          呵呵~~哥們的博客,尤其是java并發這塊,很好很好哦。  回復  更多評論
            

          # re: 捕獲Java線程池執行任務拋出的異常[未登錄] 2013-11-08 10:31 w
          dd  回復  更多評論
            

          # re: 捕獲Java線程池執行任務拋出的異常 2015-05-12 22:59 liubey
          我想問下LZ,如果主線程想拿到子線程的異常,比如展示給界面,該怎么做=。=  回復  更多評論
            

          # re: 捕獲Java線程池執行任務拋出的異常 2015-05-13 10:37 imxylz
          @liubey
          友好的做法是子線程不拋出異常,返回不同的結果,或者將異常封裝到return對象中。父對象根據此結果/異常封裝友好的提示給界面。  回復  更多評論
            

          # re: 捕獲Java線程池執行任務拋出的異常 2015-05-26 13:55 liubey
          @imxylz
          謝謝指點,你這種方式更優雅些,我自己是new了個exceptionQueue,new線程的時候set進去,然后執行完子線程后查看這個Queue  回復  更多評論
            


          ©2009-2014 IMXYLZ
          主站蜘蛛池模板: 旺苍县| 沙河市| 西青区| 师宗县| 临沭县| 西乌珠穆沁旗| 方城县| 中西区| 东莞市| 塘沽区| 湟源县| 兴仁县| 瑞金市| 商河县| 昌黎县| 青川县| 额济纳旗| 会东县| 确山县| 木里| 伊川县| 黑山县| 临颍县| 清远市| 青神县| 夏河县| 大埔县| 资源县| 浪卡子县| 临泽县| 汕尾市| 祁连县| 芷江| 绥棱县| 卓资县| 盘锦市| 威宁| 黎平县| 天峻县| 南雄市| 长宁区|