Почему UncaughtExceptionHandler не вызывается ExecutorService? - PullRequest
53 голосов
/ 03 декабря 2009

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

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

Ответы [ 6 ]

43 голосов
/ 03 декабря 2009

Потому что исключение не остается невосполнимым.

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

Этот работник отлавливает любые исключения RuntimeException из представленной работы.

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

34 голосов
/ 14 марта 2015

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

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

23 голосов
/ 23 августа 2016

Цитата из книги Параллелизм Java на практике (стр. 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 для отправки задачи и вывода на консоль "в обработчике исключений"

7 голосов
/ 21 августа 2010

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

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 голосов
/ 17 декабря 2013

В дополнение к ответу Тилоса: я написал пост об этом поведении, если кто-то хочет, чтобы он объяснил немного более подробно: https://ewirch.github.io/2013/12/a-executor-is-not-a-thread.html.

Вот выдержки из статьи:

Поток способен обрабатывать только один Runable в целом. При выходе из метода Thread.run () поток умирает. ThreadPoolExecutor реализует трюк, чтобы заставить поток обрабатывать несколько Runnables: он использует собственную реализацию Runnable. Потоки запускаются с реализацией Runnable, которая выбирает другие Runanbles (ваши Runnables) из ExecutorService и выполняет их: ThreadPoolExecutor -> Thread -> Worker -> YourRunnable. Когда в вашей реализации Runnable возникает неперехваченное исключение, оно попадает в блок finally Worker.run (). В этом окончательном блоке класс Worker сообщает ThreadPoolExecutor, что он «завершил» работу. Исключение еще не поступило в класс Thread, но ThreadPoolExecutor уже зарегистрировал работника как бездействующего.

И вот тут начинается самое интересное. Метод awaitTermination () будет вызван, когда все Runnables будут переданы Исполнителю. Это происходит очень быстро, так что, вероятно, ни один из Runnables не закончил свою работу. Работник переключится в режим ожидания, если возникнет исключение, прежде чем исключение достигнет класса Thread. Если ситуация аналогична для других потоков (или если они закончили свою работу), все работники сигнализируют «бездействует» и возвращает awaitTermination (). Основной поток достигает строки кода, где он проверяет размер собранного списка исключений. И это может произойти до того, как какой-либо (или какой-либо) из потоков сможет вызвать UncaughtExceptionHandler. Это зависит от порядка выполнения, если или сколько исключений будет добавлено в список необработанных исключений до того, как основной поток его прочитает.

Очень неожиданное поведение. Но я не оставлю тебя без рабочего решения. Итак, давайте заставим это работать.

Нам повезло, что класс ThreadPoolExecutor был разработан для расширяемости. Существует пустой защищенный метод afterExecute (Runnable r, Throwable t). Это будет вызвано непосредственно после метода run () нашего Runnable до того, как работник сообщит, что он завершил работу. Правильным решением является расширение 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);
     }
 }
2 голосов
/ 26 июля 2012

Есть небольшой обходной путь. В вашем методе run вы можете перехватить каждое исключение, а затем сделать что-то подобное (например, в блоке finally)

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...