zoukankan      html  css  js  c++  java
  • 捕获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)。

    原文地址:http://imxylz.com/blog/2013/08/02/handling-the-uncaught-exception-of-java-thread-pool/

  • 相关阅读:
    【IT学习资源】2013.10.30
    【转载】 Bash之read命令
    【书本目录】 -- ABS(advanced bash scripts)
    【转载】vSphere的使用
    【转载】 Linux命令 -- tr 转换字符
    【转载】Oracle的日常监控脚本
    【转载】Nginx基础:6.webcache缓存服务
    大学记忆(3)[三国杀(终)]
    大学记忆(1)[记忆之殇]
    大学记忆(2)[计算机]
  • 原文地址:https://www.cnblogs.com/exmyth/p/7236926.html
Copyright © 2011-2022 走看看