【问题标题】:Why is UncaughtExceptionHandler not called by ExecutorService?为什么ExecutorService不调用UncaughtExceptionHandler?
【发布时间】:2010-12-22 18:35:15
【问题描述】:

我偶然发现了一个问题,可以总结如下:

当我手动创建线程(即通过实例化java.lang.Thread)时,UncaughtExceptionHandler 会被适当地调用。但是,当我使用 ExecutorServiceThreadFactory 时,处理程序会被省略。我错过了什么?

public class ThreadStudy {

private static final int THREAD_POOL_SIZE = 1;

public static void main(String[] args) {

    // create uncaught exception handler

    final UncaughtExceptionHandler exceptionHandler = new UncaughtExceptionHandler() {

        @Override
        public void uncaughtException(Thread t, Throwable e) {
            synchronized (this) {
                System.err.println("Uncaught exception in thread '" + t.getName() + "': " + e.getMessage());
            }
        }
    };

    // create thread factory

    ThreadFactory threadFactory = new ThreadFactory() {

        @Override
        public Thread newThread(Runnable r) {
            // System.out.println("creating pooled thread");
            final Thread thread = new Thread(r);
            thread.setUncaughtExceptionHandler(exceptionHandler);
            return thread;
        }
    };

    // create Threadpool

    ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_POOL_SIZE, threadFactory);

    // create Runnable

    Runnable runnable = new Runnable() {

        @Override
        public void run() {
            // System.out.println("A runnable runs...");
            throw new RuntimeException("Error in Runnable");
        }
    };

    // create Callable

    Callable<Integer> callable = new Callable<Integer>() {

        @Override
        public Integer call() throws Exception {
            // System.out.println("A callable runs...");
            throw new Exception("Error in Callable");
        }
    };

    // a) submitting Runnable to threadpool
    threadPool.submit(runnable);

    // b) submit Callable to threadpool
    threadPool.submit(callable);

    // c) create a thread for runnable manually
    final Thread thread_r = new Thread(runnable, "manually-created-thread");
    thread_r.setUncaughtExceptionHandler(exceptionHandler);
    thread_r.start();

    threadPool.shutdown();
    System.out.println("Done.");
}
}

我期望:消息“未捕获的异常...”的三倍

我得到:消息一次(由手动创建的线程触发)。

在 Windows 7 和 Mac OS X 10.5 上使用 Java 1.6 复制。

【问题讨论】:

标签: java multithreading


【解决方案1】:

因为异常不会被捕获。

您的 ThreadFactory 生成的 Thread 没有直接给定您的 Runnable 或 Callable。相反,您获得的 Runnable 是一个内部 Worker 类,例如参见 ThreadPoolExecutor$Worker。在示例中为 newThread 提供的 Runnable 上尝试 System.out.println()

此 Worker 从您提交的作业中捕获任何 RuntimeExceptions。

您可以在ThreadPoolExecutor#afterExecute 方法中获取异常。

【讨论】:

  • 虽然这个答案是正确的,但正确实现 afterExecute 是很棘手的。有关如何正确实施此处建议的解决方案的示例,请参阅 this overflow question
  • 这里有一篇关于线程代码中异常处理的文章,包括FutureExecutorServiceliteratejava.com/threading/…
  • 这个答案并不完全正确。 UncaughtExceptionHandler 不能按预期工作的原因不是因为内部工作人员吞下了异常。它不起作用的原因是因为正在使用提交,这意味着正在创建未来并且任务在未来运行。除了实现更复杂的 afterExecute 之外,您还可以简单地获取从提交调用返回的未来并执行 whenComplete 调用来处理异常。如果您运行 threadPool.execute(runnable) 而不是 submit,您将在上述代码中看到预期的日志。
【解决方案2】:

我刚刚浏览了我的旧问题,并想我可能会分享我实施的解决方案,以防它帮助某人(或者我错过了一个错误)。

import java.lang.Thread.UncaughtExceptionHandler;
import java.util.concurrent.Callable;
import java.util.concurrent.Delayed;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.FutureTask;
import java.util.concurrent.RunnableScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;


/**
 * @author Mike Herzog, 2009
 */
public class ExceptionHandlingExecuterService extends ScheduledThreadPoolExecutor {

    /** My ExceptionHandler */
    private final UncaughtExceptionHandler exceptionHandler;

    /**
     * Encapsulating a task and enable exception handling.
     * <p>
     * <i>NB:</i> We need this since {@link ExecutorService}s ignore the
     * {@link UncaughtExceptionHandler} of the {@link ThreadFactory}.
     * 
     * @param <V> The result type returned by this FutureTask's get method.
     */
    private class ExceptionHandlingFutureTask<V> extends FutureTask<V> implements RunnableScheduledFuture<V> {

        /** Encapsulated Task */
        private final RunnableScheduledFuture<V> task;

        /**
         * Encapsulate a {@link Callable}.
         * 
         * @param callable
         * @param task
         */
        public ExceptionHandlingFutureTask(Callable<V> callable, RunnableScheduledFuture<V> task) {
            super(callable);
            this.task = task;
        }

        /**
         * Encapsulate a {@link Runnable}.
         * 
         * @param runnable
         * @param result
         * @param task
         */
        public ExceptionHandlingFutureTask(Runnable runnable, RunnableScheduledFuture<V> task) {
            super(runnable, null);
            this.task = task;
        }

        /*
         * (non-Javadoc)
         * @see java.util.concurrent.FutureTask#done() The actual exception
         * handling magic.
         */
        @Override
        protected void done() {
            // super.done(); // does nothing
            try {
                get();

            } catch (ExecutionException e) {
                if (exceptionHandler != null) {
                    exceptionHandler.uncaughtException(null, e.getCause());
                }

            } catch (Exception e) {
                // never mind cancelation or interruption...
            }
        }

        @Override
        public boolean isPeriodic() {
            return this.task.isPeriodic();
        }

        @Override
        public long getDelay(TimeUnit unit) {
            return task.getDelay(unit);
        }

        @Override
        public int compareTo(Delayed other) {
            return task.compareTo(other);
        }

    }

    /**
     * @param corePoolSize The number of threads to keep in the pool, even if
     *        they are idle.
     * @param eh Receiver for unhandled exceptions. <i>NB:</i> The thread
     *        reference will always be <code>null</code>.
     */
    public ExceptionHandlingExecuterService(int corePoolSize, UncaughtExceptionHandler eh) {
        super(corePoolSize);
        this.exceptionHandler = eh;
    }

    @Override
    protected <V> RunnableScheduledFuture<V> decorateTask(Callable<V> callable, RunnableScheduledFuture<V> task) {
        return new ExceptionHandlingFutureTask<V>(callable, task);
    }

    @Override
    protected <V> RunnableScheduledFuture<V> decorateTask(Runnable runnable, RunnableScheduledFuture<V> task) {
        return new ExceptionHandlingFutureTask<V>(runnable, task);
    }
}

【讨论】:

    【解决方案3】:

    有一点解决方法。 在您的 run 方法中,您可以捕获每个异常,然后执行类似的操作(例如:在 finally 块中)

    Thread.getDefaultUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), ex);
    //or, same effect:
    Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), ex);
    

    这将“确保触发”当前异常,如抛出给您的 uncoughtExceptionHandler (或默认的 uncought 异常处理程序)。 您可以随时为池工作者重新抛出捕获的异常。

    【讨论】:

      【解决方案4】:

      除了 Thilos 的回答:我已经写了一篇关于这种行为的帖子,如果有人想要更详细地解释它:https://ewirch.github.io/2013/12/a-executor-is-not-a-thread.html

      以下是文章的节选:

      一个线程通常只能处理一个 Runable。当 Thread.run() 方法退出时 Thread 死亡。 ThreadPoolExecutor 实现了一个技巧,使一个 Thread 进程具有多个 Runnable:它使用自己的 Runnable 实现。线程以 Runnable 实现启动,该实现从 ExecutorService 获取其他 Runanbles(您的 Runnables)并执行它们:ThreadPoolExecutor -> Thread -> Worker -> YourRunnable。当 Runnable 实现中发生未捕获的异常时,它会出现在 Worker.run() 的 finally 块中。在这个 finally 块中,Worker 类告诉 ThreadPoolExecutor 它“完成”了工作。异常尚未到达 Thread 类,但 ThreadPoolExecutor 已将 worker 注册为空闲。

      这就是乐趣的开始。当所有 Runnables 都传递给 Executor 时,将调用 awaitTermination() 方法。这种情况发生得非常快,因此可能没有一个 Runnables 完成了他们的工作。如果发生异常,Worker 将在 Exception 到达 Thread 类之前切换到“空闲”。如果其他线程的情况类似(或者如果他们完成了工作),所有的 Workers 都会发出“空闲”信号并且 awaitTermination() 返回。主线程到达检查收集的异常列表大小的代码行。这可能发生在任何(或某些)线程有机会调用 UncaughtExceptionHandler 之前。在主线程读取之前,是否将或多少个异常添加到未捕获异常列表中取决于执行顺序。

      一个非常意外的行为。但我不会让你没有一个可行的解决方案。所以让我们让它发挥作用吧。

      我们很幸运 ThreadPoolExecutor 类是为可扩展性而设计的。 afterExecute(Runnable r, Throwable t) 后有一个空的受保护方法。这将在我们的 Runnable 的 run() 方法之后直接调用,然后工作人员发出完成工作的信号。正确的解决方案是扩展 ThreadPoolExecutor 来处理未捕获的异常:

       public class ExceptionAwareThreadPoolExecutor extends ThreadPoolExecutor {
           private final List<Throwable> uncaughtExceptions = 
                           Collections.synchronizedList(new LinkedList<Throwable>());
      
           @Override
           protected void afterExecute(final Runnable r, final Throwable t) {
               if (t != null) uncaughtExceptions.add(t);
           }
      
           public List<Throwable> getUncaughtExceptions() {
               return Collections.unmodifiableList(uncaughtExceptions);
           }
       }
      

      【讨论】:

      • 在这里也可以从该帖子中添加一些细节! :)
      【解决方案5】:

      提交给ExecutorService#submit 的任务抛出的异常被包装到ExcecutionException 中,并由Future.get() 方法重新抛出。这是因为执行者将异常视为任务结果的一部分。

      如果您通过源自Executor 接口的execute() 方法提交任务,则会通知UncaughtExceptionHandler

      【讨论】:

      • 这是一个非常干净和简单的解决方案。谢谢!
      • 迄今为止最简单的解决方案。此 SO 答案中有关该行为的更多信息:stackoverflow.com/a/3986509/2874005
      • 在我的实验中,如果ExecutorService 已关闭,并且其最后一个幸存的工作线程遇到未捕获的异常,它似乎在调用处理程序之前终止。我无法从ThreadPoolExecutor 看到为什么会这样。
      • 感谢您指出submit()execute() 之间的异常处理根本不同。仔细想想是合乎逻辑的,但乍一看非常令人惊讶。由于execute()不是在ExecutorService中定义的,而是在它的超接口Executor中定义的,所以在浏览JavaDoc时很容易被忽略。可能有很多情况下使用了submit(),但execute() 是正确的。
      【解决方案6】:

      引用 Java Concurrency in Practice(第 163 页)一书,希望对您有所帮助

      有点令人困惑的是,任务抛出的异常会导致未被捕获 异常处理程序仅适用于使用执行提交的任务;对于提交的任务 通过提交,任何抛出的异常,无论是否检查,都被认为是 任务的返回状态。如果使用 submit 提交的任务因异常而终止, 它被 Future.get 重新抛出,包装在 ExecutionException 中。

      示例如下:

      public class Main {
      
      public static void main(String[] args){
      
      
          ThreadFactory factory = new ThreadFactory(){
      
              @Override
              public Thread newThread(Runnable r) {
                  // TODO Auto-generated method stub
                  final Thread thread =new Thread(r);
      
                  thread.setUncaughtExceptionHandler( new Thread.UncaughtExceptionHandler() {
      
                      @Override
                      public void uncaughtException(Thread t, Throwable e) {
                          // TODO Auto-generated method stub
                          System.out.println("in exception handler");
                      }
                  });
      
                  return thread;
              }
      
          };
      
          ExecutorService pool=Executors.newSingleThreadExecutor(factory);
          pool.execute(new testTask());
      
      }
      
      
      
      private static class TestTask implements Runnable {
      
          @Override
          public void run() {
              // TODO Auto-generated method stub
              throw new RuntimeException();
          }
      
      }
      

      我使用execute提交任务,控制台输出“in exception handler”

      【讨论】:

      • 最好将类 testTask 重命名为 TestTask
      • @JasonLaw 请原谅我的错字,已修复。谢谢
      猜你喜欢
      • 2016-07-10
      • 1970-01-01
      • 2015-06-22
      • 2011-04-28
      • 1970-01-01
      • 1970-01-01
      • 2017-05-14
      • 2011-06-22
      • 2015-05-04
      相关资源
      最近更新 更多