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

标签: java 线程池 任务 | 发表时间:2013-08-05 16:45 | 作者:imxylz
出处:http://www.blogjava.net/

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/



imxylz 2013-08-05 16:45 发表评论

相关 [java 线程池 任务] 推荐:

Java线程池

- - 企业架构 - ITeye博客
线程的使用在java中占有极其重要的地位,在jdk1.4极其之前的jdk版本中,关于线程池的使用是极其简陋的. 在jdk1.5之后这一情况有了很大的改观. Jdk1.5之后加入了java.util.concurrent包,这个包中主要介绍java中线程以及线程池的使用. 为我们在开发中处理线程的问题提供了非常大的帮助.

Java 线程池

- - 编程语言 - ITeye博客
在项目中,系统启动一个新线程的成本是比较高的,因为它涉及与操作系统交互. 在这种情形下,使用线程池可以很好地提高性能,尤其是当程序中需要创建大量生存周期很短的线程时,更应该考虑使用线程池. 使用线程池可以有效地控制系统中并发线程的数量,当系统中包含大量并发线程时,会导致系统性能剧烈下降,甚至导致JVM崩溃,而线程池的最大线程数参数可以控制系统中并发线程数不超过此数.

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

- - BlogJava-首页技术区
Java中线程执行的任务接口java.lang.Runnable 要求不抛出Checked异常,. 那么如果 run() 方法中抛出了RuntimeException,将会怎么处理了. 通常java.lang.Thread对象运行设置一个默认的异常处理方法:. 而这个默认的静态全局的异常捕获方法时输出堆栈.

java中基于线程池和反射机制实现定时任务

- - CSDN博客推荐文章
调用main方法,开始加载任务配置并执行任务. MyTask 类 实现Runnable接口,在main类中调用. TaskModel: 对任务类的封装. XmlReader 任务配置解析类. System.out.println("距离首次运行还差" + initialDelay + "秒. TaskA TaskB TaskC其中定义静态方法 ,这些类的静态方法配置在 xml文件中,被调用.

Java 如何判断线程池所有任务是否执行完毕

- - CSDN博客推荐文章
启动一次顺序关闭,执行以前提交的任务,但不接受新任务. 如果已经关闭,则调用没有其他作用. 抛出: SecurityException - 如果安全管理器存在并且关闭,此 ExecutorService 可能操作某些不允许调用者修改的线程(因为它没有保持 RuntimePermission ("modifyThread")),或者安全管理器的  checkAccess 方法拒绝访问.

java线程池分析

- - BlogJava-首页技术区
    在Java 5.0之前启动一个任务是通过调用Thread类的start()方法来实现的,任务的提于交和执行是同时进行的,如果你想对任务的执行进行调度或是控制 同时执行的线程数量就需要额外编写代码来完成. 5.0里提供了一个新的任务执行架构使你可以轻松地调度和控制任务的执行,并且可以建立一个类似数据库连接 池的线程池来执行任务.

Java线程池应用

- - CSDN博客架构设计推荐文章
1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务. 2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机). Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具.

Java线程池总结

- - Java - 编程语言 - ITeye博客
  假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间. 当T1 + T3 远大于 T2时,采用多线程技术可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力.     线程池就是一个线程的容器,每次只执行额定数量的线程, 线程池作用就是限制系统中执行线程的数量.

java 线程池原理及几种线程池详解

- - CSDN博客综合推荐文章
服务器经常出现处理大量单个任务处理的时间很短而请求的数目却是巨大的请求. 构建服务器应用程序的一个过于简单的模型应该是:每当一个请求到达就创建一个新线程,然后在新线程中为请求服务. 实际上,对于原型开发这种方法工作得很好,但如果试图部署以这种方式运行的服务器应用程序,那么这种方法的严重不足就很明显.

Java四种线程池的使用

- - ITeye博客
Java通过Executors提供四种线程池,分别为:. newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程. newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待. newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行.