`

捕获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线程池2-完善异常处理和去除同步1

    4. 最后,如果工作线程数量达到或超过最大线程数,那么拒绝执行任务,抛出`RejectedExecutionException`。 接着,`addThread`方法用于增加线程。这里使用了`synchronized`关键字来确保线程安全,避免多个线程同时...

    Java线程池学习资料-全

    Java线程池是一种高效管理并发任务的工具,它通过复用线程来减少创建和销毁线程的开销,从而提高系统性能。线程池的核心组成部分包括工作队列(Work Queue)、工作线程(Worker Threads)和任务调度器(Executor)。...

    JAVA经典线程池源码

    - `Future`接口提供获取任务结果和取消任务的能力,同时可以检查任务是否已取消或抛出异常。 - 线程池中的线程如果在执行过程中遇到未捕获的异常,线程池默认会记录这个异常并终止该线程。 通过深入研究和使用本...

    JAVA集中常用的线程池比较.pdf

    最后,线程泄漏是线程池的另一个隐患,当任务抛出未捕获的异常导致线程终止,线程不会返回到池中,线程池的大小会减少,长时间下来可能导致线程池枯竭,进而使系统无法处理新的任务。 总的来说,Java线程池是实现...

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

    首先,让我们明确一点:Java的多线程不允许未捕获的checked异常直接抛出。这意味着每个线程都必须负责处理自己产生的异常,而不是期望主线程或其他线程能捕获它们。下面通过一个简单的示例来说明这个问题: ```java...

    java 定时执行任务

    当定时任务抛出未捕获异常时,`Timer`会停止所有任务的执行。 - `ScheduledExecutorService`更为强大和安全,支持取消任务,可以创建多线程线程池,适合复杂的定时任务场景。 4. 扩展:Quartz Scheduler 对于更复杂...

    面试必备:Java线程池解析.pdf

    关于线程池的异常处理,提交给线程池的任务可能会抛出RuntimeException,线程池在捕获这些异常后可能会创建新线程代替异常线程继续执行,或者直接忽略。因此,在使用线程池时,需要对任务的异常处理进行适当的考虑。...

    子线程任务发生异常,主线程事务如何回滚

    如果子线程执行过程中发生异常,Future 对象将抛出异常。我们可以使用 try-catch 语句来捕获异常,并进行事务的回滚。 当子线程任务发生异常时,如何让主线程捕获到该异常并进行事务的回滚?答案是使用 Callable ...

    Java编程中线程池的最大风险规避

    当工作线程在执行任务时抛出未被捕获的运行时异常或错误时,该线程将被异常终止,导致线程池永久失去一个工作线程。如果所有的工作线程都因未处理的异常而终止,线程池将变得不可用。为了避免这种情况,可以在创建...

    java通过线程控制程序执行超时

    当`get()`方法超过指定时间仍未返回时,会抛出`TimeoutException`,这时我们可以捕获异常并采取相应措施,如取消任务或记录日志。 除了这种方式,我们还可以利用`java.util.Timer`类和`TimerTask`来实现超时控制。`...

    java通过Callable和Future来接收线程池的执行结果

    如果线程执行过程中出现了错误,那么Future的get方法也会抛出异常,从而可以及时地感知到线程执行中的错误。 在示例代码中,我们使用了CountDownLatch来等待所有线程执行完毕,然后再关闭线程池。我们可以看到,在...

    线程终止异常如何解决.md

    在提供的Java示例代码中,展示了如何使用ExecutorService线程池执行任务,并在任务中处理可能发生的ArithmeticException异常。ExecutorService是一个线程池实现,负责管理线程的生命周期,包括创建、执行、和回收...

    线程池的submit和execute的区别.md

    此外,如果任务执行过程中抛出异常,主线程通常也无法捕获这些异常信息。 ##### 2.2 使用场景 由于`execute()`方法的特性,它适用于那些对任务执行结果无特殊需求或者能够通过其他方式间接获取结果的场景。例如,在...

    云外归鸟的线程池支持库

    10. **异常处理**:线程池会捕获并处理执行任务过程中发生的异常,确保线程池的稳定运行,防止因个别任务异常导致整个线程池崩溃。 "云外归鸟的线程池支持库"可能是针对Java或其他语言的一个特定实现,它可能提供了...

    java多线程,对多线程,线程池进行封装,方便使用

    当线程抛出未捕获异常时,线程池有默认的处理策略,例如`ThreadPoolExecutor.AbortPolicy`会终止线程并抛出异常,开发者也可以自定义策略。 7. **Future和Callable接口** 使用`Future`接口可以获取线程执行的结果...

    java多线程、并发及线程池介绍收藏的几篇文档

    - 异常处理:线程中抛出的异常不会影响主线程,需通过`Thread.UncaughtExceptionHandler`设置未捕获异常处理器。 6. **使用Java实现多线程服务器程序** - 对于服务器应用,通常使用多线程来处理客户端请求,每个...

    多线程异常处理.pdf

    在处理多线程异常时,需要考虑线程池的使用情况,由于线程池通常会重用线程,对于抛出异常的线程,需要有机制来处理,例如在Java的ExecutorService线程池中可以为每个任务指定一个Callable,它允许返回一个结果并抛...

    android线程池demo

    线程池还提供了异常处理机制,可以通过`Thread.UncaughtExceptionHandler`来捕获并处理任务执行过程中抛出的未捕获异常。 ### 总结 `android线程池`是Android应用中进行多线程操作的重要工具,它可以帮助我们更有效...

    基于Java子线程中的异常处理方法(通用)

    如果子线程发生了异常,get()方法将抛出ExecutionException异常,可以在父线程中捕获和处理。 结论 在Java子线程中处理异常是一个复杂的问题,但通过使用try ... catch ...、设置异常处理器和使用Future和Callable...

    总结java的30个异常及方案

    `try`块用于包含可能会抛出异常的代码,`catch`块用于捕获并处理异常,`finally`块保证了无论是否发生异常,某些代码总会被执行。 4. **多异常捕获**:可以使用`catch`块的括号内列出多个异常类型,用逗号分隔,...

Global site tag (gtag.js) - Google Analytics