次のように要約できる問題に遭遇しました。
スレッドを手動で作成すると(つまり、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.");
}
}
私は期待します:メッセージ「キャッチされなかった例外...」の3回
取得:メッセージを1回(手動で作成したスレッドによってトリガー)。
Java 1.6をWindows 7およびMac OS X 10.5で再現。
例外がキャッチされないためです。
ThreadFactoryが生成するスレッドには、直接RunnableまたはCallableが与えられません。代わりに、取得するRunnableは内部Workerクラスです。たとえば、ThreadPoolExecutor $ Workerを参照してください。例のnewThreadに指定されたRunnableでSystem.out.println()
を試してください。
このワーカーは、送信されたジョブからのRuntimeExceptionをキャッチします。
ThreadPoolExecutor#afterExecute メソッドで例外を取得できます。
_ExecutorService#submit
_に送信されたタスクによってスローされた例外はExcecutionException
にラップされ、Future.get()
メソッドによって再スローされます。これは、executorが例外をタスクの結果の一部と見なすためです。
ただし、Executor
インターフェイスから発生するexecute()
メソッドを介してタスクを送信すると、UncaughtExceptionHandler
に通知されます。
本からの引用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を使用してタスクを送信すると、コンソールに「例外ハンドラー内」が出力されます
私は私の古い質問を閲覧しただけで、誰かに役立つ(またはバグを見逃した)場合に備えて、実装したソリューションを共有できると思いました。
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);
}
}
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); } }
回避策が少しあります。 run
メソッドでは、すべての例外をキャッチし、後でこのようなことを行うことができます(例:finally
ブロック内)
Thread.getDefaultUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), ex);
//or, same effect:
Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), ex);
これにより、uncoughtExceptionHandler(またはデフォルトのunought例外ハンドラー)にスローされた現在の例外が「確実に発生」します。プールワーカーのキャッチした例外はいつでも再スローできます。