捕获Java线程池执行任务抛出的异常
2015-07-03 15:34 Loull 阅读(3581) 评论(0) 收藏 举报Java中线程执行的任务接口java.lang.Runnable 要求不抛出Checked异常,
public interface Runnable { public abstract void run(); }
那么如果 run() 方法中抛出了RuntimeException,将会怎么处理了?
通常java.lang.Thread对象运行设置一个默认的异常处理方法:
java.lang.Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler)
而这个默认的静态全局的异常捕获方法是直接输出异常堆栈。
当然,我们可以覆盖此默认实现,只需要一个自定义的java.lang.Thread.UncaughtExceptionHandler接口实现即可。
public interface UncaughtExceptionHandler { void uncaughtException(Thread t, Throwable e); }
而在线程池中却比较特殊。默认情况下,线程池 java.util.concurrent.ThreadPoolExecutor 会Catch住所有异常, 当任务执行完成(java.util.concurrent.ExecutorService.submit(Callable))获取其结果 时(java.util.concurrent.Future.get())会抛出此RuntimeException。
/** * Waits if necessary for the computation to complete, and then * retrieves its result. * * @return the computed result * @throws CancellationException if the computation was cancelled * @throws ExecutionException if the computation threw an exception * @throws InterruptedException if the current thread was interrupted while waiting */ V get() throws InterruptedException, ExecutionException;
其中 ExecutionException 异常即是java.lang.Runnable 或者 java.util.concurrent.Callable 抛出的异常。
也就是说,线程池在执行任务时捕获了所有异常,并将此异常加入结果中。这样一来线程池中的所有线程都将无法捕获到抛出的异常。 从而无法通过设置线程的默认捕获方法拦截的错误异常。
也不同通过自定义线程来完成异常的拦截。
好在java.util.concurrent.ThreadPoolExecutor 预留了一个方法,运行在任务执行完毕进行扩展(当然也预留一个protected方法beforeExecute(Thread t, Runnable r)):
protected void afterExecute(Runnable r, Throwable t) { }
此方法的默认实现为空,这样我们就可以通过继承或者覆盖ThreadPoolExecutor 来达到自定义的错误处理。
解决办法如下:
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(11, 100, 1, TimeUnit.MINUTES, // new ArrayBlockingQueue<Runnable>(10000),// new DefaultThreadFactory()) { protected void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); printException(r, t); } }; private static void printException(Runnable r, Throwable t) { if (t == null && r instanceof Future<?>) { try { Future<?> future = (Future<?>) r; if (future.isDone()) future.get(); } catch (CancellationException ce) { t = ce; } catch (ExecutionException ee) { t = ee.getCause(); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); // ignore/reset } } if (t != null) log.error(t.getMessage(), t); }
此办法的关键在于,事实上 afterExecute 并不会总是抛出异常 Throwable t,通过查看源码得知,异常是封装在此时的Future对象中的, 而此Future对象其实是一个java.util.concurrent.FutureTask的实现,默认的run方法其实调用的 java.util.concurrent.FutureTask.Sync.innerRun()。
void innerRun() { if (!compareAndSetState(0, RUNNING)) return; try { runner = Thread.currentThread(); if (getState() == RUNNING) // recheck after setting thread innerSet(callable.call()); else releaseShared(0); // cancel } catch (Throwable ex) { innerSetException(ex); } } void innerSetException(Throwable t) { for (;;) { int s = getState(); if (s == RAN) return; if (s == CANCELLED) { // aggressively release to set runner to null, // in case we are racing with a cancel request // that will try to interrupt runner releaseShared(0); return; } if (compareAndSetState(s, RAN)) { exception = t; result = null; releaseShared(0); done(); return; } } }
这里我们可以看到它吃掉了异常,将异常存储在java.util.concurrent.FutureTask.Sync的exception字段中:
/** The exception to throw from get() */ private Throwable exception;
当我们获取异步执行的结果时, java.util.concurrent.FutureTask.get()
public V get() throws InterruptedException, ExecutionException { return sync.innerGet(); }
java.util.concurrent.FutureTask.Sync.innerGet()
V innerGet() throws InterruptedException, ExecutionException { acquireSharedInterruptibly(0); if (getState() == CANCELLED) throw new CancellationException(); if (exception != null) throw new ExecutionException(exception); return result; }
异常就会被包装成ExecutionException异常抛出。
也就是说当我们想线程池 ThreadPoolExecutor(java.util.concurrent.ExecutorService)提交任务时, 如果不理会任务结果(Feture.get()),那么此异常将被线程池吃掉。
<T> Future<T> submit(Callable<T> task);
Future<?> submit(Runnable task);
而java.util.concurrent.ScheduledThreadPoolExecutor是继承ThreadPoolExecutor的,因此情况类似。
结论,通过覆盖ThreadPoolExecutor.afterExecute 方法,我们才能捕获到任务的异常(RuntimeException)。
原文地址:http://imxylz.com/blog/2013/08/02/handling-the-uncaught-exception-of-java-thread-pool/
Q:如果主线程想拿到子线程的异常,比如展示给界面,该怎么做?
A:友好的做法是子线程不抛出异常,返回不同的结果,或者将异常封装到return对象中。父对象根据此结果/异常封装友好的提示给界面。
浙公网安备 33010602011771号