Java 为什么 ExecutorService 没有调用 UncaughtExceptionHandler?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1838923/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 23:09:51  来源:igfitidea点击:

Why is UncaughtExceptionHandler not called by ExecutorService?

javamultithreading

提问by DerMike

I've stumbled upon a problem, that can be summarized as follows:

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

When I create the thread manually (i.e. by instantiating java.lang.Thread) the UncaughtExceptionHandleris called appropriately. However, when I use an ExecutorServicewith a ThreadFactorythe handler is ommited. What did I miss?

当我手动创建线程(即通过实例化java.lang.Thread)时,UncaughtExceptionHandler它会被适当地调用。但是,当我将 anExecutorService与 a 一起使用时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.");
}
}

I expect: Three times the message "Uncaught exception..."

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

I get: The message once (triggered by the manually created thread).

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

Reproduced with Java 1.6 on Windows 7 and Mac OS X 10.5.

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

采纳答案by Thilo

Because the exception does not go uncaught.

因为异常不会未被捕获。

The Thread that your ThreadFactory produces is not given your Runnable or Callable directly. Instead, the Runnable that you get is an internal Worker class, for example see ThreadPoolExecutor$Worker. Try System.out.println()on the Runnable given to newThread in your example.

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

This Worker catches any RuntimeExceptions from your submitted job.

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

You can get the exception in the ThreadPoolExecutor#afterExecutemethod.

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

回答by DerMike

I just browsed through my old questions and thought I might share the solution I implemented in case it helps someone (or I missed a bug).

我只是浏览了我的旧问题,并认为我可以分享我实施的解决方案,以防它对某人有所帮助(或者我错过了错误)。

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

回答by Antoniossss

There is a little bit of a workaround. In your runmethod, you can catch every exception, and later on do something like this (ex: in a finallyblock)

有一点解决方法。在你的run方法中,你可以捕获每个异常,然后做这样的事情(例如:在一个finally块中)

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

This will "ensure a firing" of the current exception as thrown to your uncoughtExceptionHandler (or to the defualt uncought exception handler). You can always rethrow catched exceptions for pool worker.

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

回答by Eduard Wirch

In addition to Thilos answer: I've written a post about this behavior, if one wants to have it explained a little bit more verbose: https://ewirch.github.io/2013/12/a-executor-is-not-a-thread.html.

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

Here is a excerpts from the article:

以下是文章的节选:

A Thread is capable of processing only one Runable in general. When the Thread.run() method exits the Thread dies. The ThreadPoolExecutor implements a trick to make a Thread process multiple Runnables: it uses a own Runnable implementation. The threads are being started with a Runnable implementation which fetches other Runanbles (your Runnables) from the ExecutorService and executes them: ThreadPoolExecutor -> Thread -> Worker -> YourRunnable. When a uncaught exception occurs in your Runnable implementation it ends up in the finally block of Worker.run(). In this finally block the Worker class tells the ThreadPoolExecutor that it “finished” the work. The exception did not yet arrive at the Thread class but ThreadPoolExecutor already registered the worker as idle.

And here's where the fun begins. The awaitTermination() method will be invoked when all Runnables have been passed to the Executor. This happens very quickly so that probably not one of the Runnables finished their work. A Worker will switch to “idle” if a exception occurs, before the Exception reaches the Thread class. If the situation is similar for the other threads (or if they finished their work), all Workers signal “idle” and awaitTermination() returns. The main thread reaches the code line where it checks the size of the collected exception list. And this may happen before any (or some) of the Threads had the chance to call the UncaughtExceptionHandler. It depends on the order of execution if or how many exceptions will be added to the list of uncaught exceptions, before the main thread reads it.

A very unexpected behavior. But I won't leave you without a working solution. So let's make it work.

We are lucky that the ThreadPoolExecutor class was designed for extensibility. There is a empty protected method afterExecute(Runnable r, Throwable t). This will be invoked directly after the run() method of our Runnable before the worker signals that it finished the work. The correct solution is to extend the ThreadPoolExecutor to handle uncaught exceptions:

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

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

这就是乐趣开始的地方。当所有 Runnable 都传递给 Executor 时,将调用 awaitTermination() 方法。这发生得非常快,因此可能没有一个 Runnables 完成他们的工作。如果异常发生,在异常到达 Thread 类之前,Worker 将切换到“空闲”。如果其他线程的情况类似(或者如果它们完成了工作),则所有 Worker 都发出“空闲”信号并且 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);
     }
 }

回答by Jan Trienes

Exceptions which are thrown by tasks submitted to ExecutorService#submitget wrapped into an ExcecutionExceptionand are rethrown by the Future.get()method. This is, because the executor considers the exception as part of the result of the task.

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

If you however submit a task via the execute()method which originates from the Executorinterface, the UncaughtExceptionHandleris notified.

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

回答by Chaojun Zhong

Quote from the book Java Concurrency in Practice(page 163),hope this helps

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

Somewhat confusingly, exceptions thrown from tasks make it to the uncaught exception handler only for tasks submitted with execute; for tasks submitted with submit, any thrown exception, checked or not, is considered to be part of the task's return status. If a task submitted with submit terminates with an exception, it is rethrown by Future.get, wrapped in an ExecutionException.

有点令人困惑的是,从任务中抛出的异常只针对使用 execute 提交的任务才进入未捕获的异常处理程序;对于使用 submit 提交的任务,任何抛出的异常,无论是否检查,都被视为任务返回状态的一部分。如果使用 submit 提交的任务因异常终止,则 Future.get 将其重新抛出,并包含在 ExecutionException 中。

Here is the example:

这是示例:

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

}

I use execute to submit the task and the console outputs "in exception handler"

我使用执行提交任务,控制台输出“在异常处理程序中”