Почему UncaughtExceptionHandler не вызван ExecutorService?

Я наткнулся на проблему, которую можно суммировать следующим образом:

Когда я создаю поток вручную (т.е. путем создания экземпляра 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.");
}
}

Я ожидаю: три раза сообщение "Неотключить исключение..."

Я получаю: одно сообщение (вызванное созданным вручную потоком).

Воспроизводится с помощью Java 1.6 в Windows 7 и Mac OS X 10.5.

Ответ 1

Потому что исключение не идет неотображаемым.

Поток, который производит ваш ThreadFactory, напрямую не передается вашим Runnable или Callable. Вместо этого Runnable, который вы получаете, является внутренним классом Worker, например, см. ThreadPoolExecutor $Worker. Попробуйте System.out.println() в Runnable, заданном newThread в вашем примере.

Этот Рабочий ловит любые RuntimeExceptions из вашего отправленного задания.

Вы можете получить исключение в методе ThreadPoolExecutor # afterExecute.

Ответ 2

Исключения, которые выполняются задачами, переданными в ExecutorService#submit, завершаются в ExcecutionException и возвращаются методом Future.get(). Это потому, что исполнитель рассматривает исключение как часть результата задачи.

Если вы отправляете задачу с помощью метода execute(), который исходит из интерфейса Executor, уведомляется UncaughtExceptionHandler.

Ответ 3

Цитата из книги Java Concurrency на практике (стр. 163), надеюсь, что это поможет

Несколько смехотворно, исключения, отбрасываемые из задач, превращаются в невкусные обработчик исключений только для задач, отправленных с выполнением; для поставленных задач с подачей, любое исключенное исключение, проверенное или нет, считается частью статус возврата задачи. Если задание, отправленное с отправлением, завершается с исключением, он заново свернут 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 для отправки задачи, а выходы консоли "в обработчике исключений"

Ответ 4

Я только просматривал свои старые вопросы и думал, что могу поделиться решением, которое я реализовал, в случае, если он помогает кому-то (или я пропустил ошибку).

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

Ответ 5

Существует немного обходного пути. В вашем методе запуска вы можете поймать каждое исключение, а затем сделать что-то вроде этого

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

Это будет "зарегистрировать" текущее исключение, которое будет передано вашему uncoughttception обработчику исключений. Вы всегда можете отменить захваченные исключения для рабочего пула.