`

捕获Java线程池执行任务抛出的异常

 
阅读更多

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)。

 

分享到:
评论

相关推荐

    Java多线程之多线程异常捕捉

    在java多线程程序中,所有线程都不允许抛出未捕获的checked exception,也就是说各个线程需要自己把自己的checked exception处理掉,通过此篇文章给大家分享Java多线程之多线程异常捕捉,需要的朋友可以参考下

    疯狂JAVA讲义

    10.3.1 使用throws声明抛出异常 367 10.4 使用throw抛出异常 369 10.4.1 抛出异常 369 10.4.2 自定义异常类 371 10.4.3 catch和throw同时使用 371 10.4.4 异常链 373 10.5 Java的异常跟踪栈 374 10.6 异常...

    java,c/c++,php,c#安全编码规范

    2.6.5 相互依存的任务不要在一个有限的线程池执行 27 2.7 输入输出 27 2.7.1 程序终止前删除临时文件 27 2.7.2 检测和处理文件相关的错误 27 2.7.3 及时释放资源 27 2.8 序列化 28 2.8.1 不要序列化未加密的敏感数据...

    java多线程中的异常处理机制简析

    在java多线程程序中,所有线程都不允许抛出未捕获的checked exception,也就是说各个线程需要自己把自己的checked exception处理掉,需要了解的朋友可以参考下

    JAVA面试题最全集

    如果抛出一个异常,那么相匹配的 catch 子句就会执行,然后控制就会进入 finally 块(如果有的话)。 finalize?方法名。Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的...

    java范例开发大全源代码

     实例51 throw抛出异常实例一 72  实例52 throw抛出异常实例二 73  4.6 自定义异常 74  实例53 自定义异常实例一 74  实例54 自定义异常实例二 75  第5章 数组(教学视频:98分钟) 78  5.1 一维...

    java范例开发大全

    实例51 throw抛出异常实例一 72 实例52 throw抛出异常实例二 73 4.6 自定义异常 74 实例53 自定义异常实例一 74 实例54 自定义异常实例二 75 第5章 数组(教学视频:98分钟) 78 5.1 一维数组 78 实例55 一维数组的...

    Java范例开发大全 (源程序)

     实例51 throw抛出异常实例一 72  实例52 throw抛出异常实例二 73  4.6 自定义异常 74  实例53 自定义异常实例一 74  实例54 自定义异常实例二 75  第5章 数组(教学视频:98分钟) 78  5.1 一维数组 ...

    java范例开发大全(pdf&源码)

    实例51 throw抛出异常实例一 72 实例52 throw抛出异常实例二 73 4.6 自定义异常 74 实例53 自定义异常实例一 74 实例54 自定义异常实例二 75 第5章 数组(教学视频:98分钟) 78 5.1 一维数组 78 实例55 一维数组的...

    Java范例开发大全(全书源程序)

    实例51 throw抛出异常实例一 72 实例52 throw抛出异常实例二 73 4.6 自定义异常 74 实例53 自定义异常实例一 74 实例54 自定义异常实例二 75 第5章 数组(教学视频:98分钟) 78 5.1 一维数组 78 实例55 一...

    Java开发实战1200例(第1卷).(清华出版.李钟尉.陈丹丹).part3

    实例171 方法上抛出异常 219 实例172 自定义异常类 220 实例173 捕获单个异常 221 实例174 捕获多个异常 222 第8章 枚举与泛型的应用 223 8.1 枚举使用的简介 224 实例175 查看枚举类型的定义 224 实例176 枚举类型...

    javaSE代码实例

    11.3.4 方法重写对抛出异常声明的约束 210 11.4 定义自己的异常 212 11.4.1 创建自己的异常类 212 11.4.2 使用自定义的异常类 213 11.4.3 显性再抛出作用的体现 215 11.5 异常的匹配 217 11.5.1 同时...

Global site tag (gtag.js) - Google Analytics