• 捕获Java线程池执行任务抛出的异常


       在学习编程的过程中,我觉得不止要获得课本的知识,更多的是通过学习技术知识提高解决问题的能力,这样我们才能走在最前方,本文主要讲述捕获Java线程池执行任务抛出的异常,更多Java专业知识,广州疯狂java培训为你讲解;

      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(11, 100, 1, TimeUnit.MINUTES, //

      new ArrayBlockingQueue(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()),那么此异常将被线程池吃掉。

      Future submit(Callable task);

      Future submit(Runnable task);

      而java.util.concurrent.ScheduledThreadPoolExecutor是继承ThreadPoolExecutor的,因此情况类似。

      结论,通过覆盖ThreadPoolExecutor.afterExecute 方法,我们才能捕获到任务的异常(RuntimeException)。

      疯狂Java培训专注软件开发培训,提升学员就业能力,重点提升实践动手能力。没有工作经验的学员,在疯狂java,通过大量全真经典企业级项目进行集中培训,课上讲师讲解和课下项目练习课上课下双管齐下,学员通过数月培训都可获得1-2年的工作经验,进而在同类的求职者中脱颖而出。疯狂Java培训让你体会java编程的快乐,项目开发的兴奋,激情,通过短短几个月的时间,让你在半年的时间内掌握8-10万的代码量,掌握Java核心技术,迅速成为技能型的现代化高端人才,迅速获得高薪就业!

  • 相关阅读:
    操作标签的属性和属性值 table表格
    dom基本获取 标签文本操作
    延时器 清除延时器
    倒计时
    电子时钟
    时间戳
    设定时间的方法
    内置对象Date
    对象的基本特点
    终于有人把云计算、大数据和 AI 讲明白了【深度好文】
  • 原文地址:https://www.cnblogs.com/gojava/p/3252449.html
Copyright © 2020-2023  润新知