为什么ExecutorService没有调用UncaughtExceptionHandler?

问题描述 投票:0回答:6

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

当我手动创建线程时(即通过实例化

java.lang.Thread
),
UncaughtExceptionHandler
会被适当地调用。但是,当我将
ExecutorService
ThreadFactory
一起使用时,处理程序将被忽略。我错过了什么?

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
6个回答
54
投票

因为异常不会被捕获。

您的 ThreadFactory 生成的线程不会直接提供给您的 Runnable 或 Callable。相反,您获得的 Runnable 是内部 Worker 类,例如请参阅 ThreadPoolExecutor$Worker。在示例中为 newThread 提供的 Runnable 上尝试

System.out.println()

此 Worker 从您提交的作业中捕获任何运行时异常。

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


50
投票

提交给

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

但是,如果您通过源自

execute()
接口的
Executor
方法提交任务,则会通知
UncaughtExceptionHandler


38
投票

引自《Java并发实践》一书(第163页),希望对你有帮助

有点令人困惑的是,任务抛出的异常会导致未被捕获 异常处理程序仅适用于使用
execute

提交的任务;对于提交的任务 使用

submit
,任何抛出的异常,无论是否检查,都被认为是 任务的返回状态。如果使用 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提交任务,控制台输出“在异常处理程序中”


7
投票

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); } }



6
投票
https://ewirch.github.io/2013/12/a-executor-is -not-a-thread.html

. 以下是文章摘录:

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

这就是乐趣的开始。当所有 Runnables 都被传递给 Executor 时,awaitTermination() 方法将被调用。这种情况发生得非常快,因此可能没有一个 Runnable 完成他们的工作。如果发生异常,在异常到达 Thread 类之前,Worker 将切换到“空闲”状态。如果其他线程的情况类似(或者如果它们完成了工作),则所有 Workers 都会发出“空闲”信号,并且 waitTermination() 返回。主线程到达检查收集的异常列表的大小的代码行。这可能发生在任何(或某些)线程有机会调用 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); } }

    

3
投票
run

方法中,您可以捕获每个异常,然后执行类似的操作(例如:在

finally
块中)

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

这将“确保触发”当前异常,并将其抛出到您的 uncoughtExceptionHandler (或默认的 uncought 异常处理程序)。 
您始终可以重新抛出池工作人员捕获的异常。

© www.soinside.com 2019 - 2024. All rights reserved.