为什么ExecutorService不调用UncaughtExceptionHandler?

时间:2009-12-03 10:04:30

标签: java multithreading

我偶然发现了一个问题,可归纳如下:

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

ThreadFactory

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

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

在Windows 7和Mac OS X 10.5上使用Java 1.6重现。

6 个答案:

答案 0 :(得分:43)

因为例外不​​会被删除。

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

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

您可以使用ThreadPoolExecutor#afterExecute方法获取例外。

答案 1 :(得分:34)

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

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

答案 2 :(得分:23)

引用书 Java Concurrency in Practice (第163页),希望这有帮助

  

有些令人困惑的是,从任务中抛出的异常使其成为未被捕获的   异常处理程序仅用于使用execute提交的任务;提交的任务   有了提交,任何抛出的异常,无论是否被检查,都被认为是其中的一部分   任务的返回状态。如果使用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来提交任务,控制台输出“在异常处理程序中”

答案 3 :(得分:6)

我只是浏览了我的旧问题,并认为我可以分享我实施的解决方案以防万一(或者我错过了一个错误)。

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

答案 4 :(得分:4)

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

以下是文章摘录:

  

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

     

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

     

非常意外的行为。但如果没有可行的解决方案,我不会离开你。所以,让它成功。

     

我们很幸运,ThreadPoolExecutor类是为可扩展性而设计的。在执行之后有一个空的受保护方法(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 :(得分:2)

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

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

这将“确保将当前异常的触发”抛出到你的uncoughtExceptionHandler(或defualt unought异常处理程序)。 您始终可以为池工作者重新提取捕获的异常。