web-dev-qa-db-ja.com

UncaughtExceptionHandlerがExecutorServiceによって呼び出されないのはなぜですか?

次のように要約できる問題に遭遇しました。

スレッドを手動で作成すると(つまり、Java.lang.Threadをインスタンス化して)、UncaughtExceptionHandlerが適切に呼び出されます。ただし、ExecutorServiceThreadFactoryと共に使用すると、ハンドラーが省略されます。私は何を取りこぼしたか?

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

私は期待します:メッセージ「キャッチされなかった例外...」の3回

取得:メッセージを1回(手動で作成したスレッドによってトリガー)。

Java 1.6をWindows 7およびMac OS X 10.5で再現。

54
DerMike

例外がキャッチされないためです。

ThreadFactoryが生成するスレッドには、直接RunnableまたはCallableが与えられません。代わりに、取得するRunnableは内部Workerクラスです。たとえば、ThreadPoolExecutor $ Workerを参照してください。例のnewThreadに指定されたRunnableでSystem.out.println()を試してください。

このワーカーは、送信されたジョブからのRuntimeExceptionをキャッチします。

ThreadPoolExecutor#afterExecute メソッドで例外を取得できます。

44
Thilo

_ExecutorService#submit_に送信されたタスクによってスローされた例外はExcecutionExceptionにラップされ、Future.get()メソッドによって再スローされます。これは、executorが例外をタスクの結果の一部と見なすためです。

ただし、Executorインターフェイスから発生するexecute()メソッドを介してタスクを送信すると、UncaughtExceptionHandlerに通知されます。

34
Jan Trienes

本からの引用Java Concurrency in Practice(163ページ)、これが役立つことを願って

やや混乱しますが、タスクからスローされた例外は、executeでサブミットされたタスクに対してのみ、キャッチされない例外ハンドラーになります。 submitで送信されたタスクの場合、スローされた例外は、チェックされているかどうかにかかわらず、タスクの戻りステータスの一部と見なされます。 submitで送信されたタスクが例外で終了した場合、ExecutionExceptionにラップされたFuture.getによって再スローされます。

次に例を示します。

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を使用してタスクを送信すると、コンソールに「例外ハンドラー内」が出力されます

23
Chaojun Zhong

私は私の古い質問を閲覧しただけで、誰かに役立つ(またはバグを見逃した)場合に備えて、実装したソリューションを共有できると思いました。

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

Thilosの回答に加えて、私はこの動作について投稿しました。もう少し詳細に説明したい場合は、 https://ewirch.github.io/2013/12/a-executor -is-not-a-thread.html

これは記事からの抜粋です:

スレッドは通常、1つのRunableのみを処理できます。 Thread.run()メソッドが終了すると、スレッドは終了します。 ThreadPoolExecutorは、スレッドが複数のRunnableを処理するためのトリックを実装します。独自のRunnable実装を使用します。スレッドは、ExecutorServiceから他のRunanble(ユーザーのRunnable)をフェッチして実行するRunnable実装で開始されます:ThreadPoolExecutor-> Thread-> Worker-> YourRunnable。 Runnable実装でキャッチされない例外が発生すると、Worker.run()のfinallyブロックになります。この最後のブロックでは、WorkerクラスがThreadPoolExecutorに作業が「終了」したことを通知します。例外はまだThreadクラスに到着していませんが、ThreadPoolExecutorはすでにワーカーをアイドルとして登録しています。

そして、ここから楽しみが始まります。すべてのRunnableがエグゼキューターに渡されると、awaitTermination()メソッドが呼び出されます。これは非常に迅速に発生するため、おそらくRunnableの1つが作業を完了していません。例外が発生すると、例外がスレッドクラスに到達する前に、ワーカーは「アイドル」に切り替わります。他のスレッドの状況が似ている場合(または作業が終了した場合)、すべてのワーカーが「アイドル」のシグナルを送り、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);
     }
 }
4
Eduard Wirch

回避策が少しあります。 runメソッドでは、すべての例外をキャッチし、後でこのようなことを行うことができます(例:finallyブロック内)

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

これにより、uncoughtExceptionHandler(またはデフォルトのunought例外ハンドラー)にスローされた現在の例外が「確実に発生」します。プールワーカーのキャッチした例外はいつでも再スローできます。

2
Antoniossss