线程池线程异常捕获


线程池中的一个线程异常了会发生什么?

参考文档

https://zhuanlan.zhihu.com/p/136571068

  1. 线程抛出异常信息
  2. 异常线程被线程池回收;线程不是被回收而是线程池把这个线程移除掉,同时创建一个新的线程放到线程池中。
  3. 其他非异常线程不受影响,继续执行

多线程(线程池)异常捕获

Executors 线程池

线程池有两种提交线程的方式,execute() 和 submit() ,使用时有以下区别:

  • execute 没有返回值。可以执行任务,但无法判断任务是否成功完成。
    • 实现 Runnable 接口
  • submit 返回一个 future ,可以用这个 future 来判断任务是否成功完成。
    • 实现 Callable 接口
public static void main(String[] args) {
    ThreadPoolTaskExecutor executorService = buildThreadPoolTaskExecutor();

    executorService.execute(() -> run("execute方法"));

    executorService.submit(() -> run("submit方法"));
}

private static void run(String name) {
    String printStr = "【thread-name:" + Thread.currentThread().getName() + ",执行方式:" + name+"】";
    System.out.println(printStr);
    throw new RuntimeException(printStr + ",出现异常");
}

private static ThreadPoolTaskExecutor buildThreadPoolTaskExecutor() {
    ThreadPoolTaskExecutor executorService = new ThreadPoolTaskExecutor();
    executorService.setThreadNamePrefix("(小罗技术笔记)-");
    executorService.setCorePoolSize(5);
    executorService.setMaxPoolSize(10);
    executorService.setQueueCapacity(100);
    executorService.setKeepAliveSeconds(10);
    executorService.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    executorService.initialize();
    return executorService;
}
  • 运行程序, execute 执行方式抛出异常显示在控制台了;submit 执行程序并没有任何异常打印

这是因为 submit() 方法执行线程后会返回线程执行结果,而相应的执行异常信息也被放入到结果中了,可以通过结果对象来获取异常信息

Future<?> result=executorService.submit(() -> run("submit方法"));
try &#123;
    result.get();
&#125;catch (Exception e)&#123;
    e.printStackTrace();
&#125;
  • 如果线程抛出异常,则使用 get() 获取结果时会抛出异常信息

捕获线程池线程异常

参考:https://www.cnblogs.com/549294286/p/4618798.html

Java 中线程执行的任务接口 java.lang.Runnable 要求不抛出 Checked 异常,但是当实现的 run() 方法中出现了异常,会怎么样呢?又怎么啦捕获异常信息呢?

通常 java.lang.Thread 对象运行设置一个默认的异常处理方法,这个默认的静态全局的异常捕获方法是直接输出异常堆栈。

java.lang.Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler)

我们可以覆盖此默认实现,只需要一个自定义的 java.lang.Thread.UncaughtExceptionHandler 接口实现即可。

public interface UncaughtExceptionHandler &#123;
    void uncaughtException(Thread t, Throwable e);
&#125;

线程池中却比较特殊,默认情况下,线程池 java.util.concurrent.ThreadPoolExecutor 会 Catch 住所有异常, 当任务执行完成 (java.util.concurrent.ExecutorService.submit(Callable)) 获取其结果时 Future.get()) 会抛出此异常信息 RuntimeException 。

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) &#123; &#125;

此方法的默认实现为空,这样我们就可以通过继承或者覆盖ThreadPoolExecutor 来达到自定义的错误处理。

重写线程池 afterExecute() 方法实现异常捕获

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(11, 100, 1, TimeUnit.MINUTES, //
        new ArrayBlockingQueue<Runnable>(10000),//
        new DefaultThreadFactory()) &#123;
    @Override
    protected void afterExecute(Runnable r, Throwable t) &#123;
        super.afterExecute(r, t);
        printException(r, t);
    &#125;
&#125;;

private static void printException(Runnable r, Throwable t) &#123;
    if (t == null && r instanceof Future<?>) &#123;
        try &#123;
            Future<?> future = (Future<?>) r;
            if (future.isDone())
                future.get();
        &#125; catch (CancellationException ce) &#123;
            t = ce;
        &#125; catch (ExecutionException ee) &#123;
            t = ee.getCause();
        &#125; catch (InterruptedException ie) &#123;
            Thread.currentThread().interrupt(); // ignore/reset
        &#125;
    &#125;
    if (t != null)
        log.error(t.getMessage(), t);
&#125;
  • 如果使用 @Configuration + @Bean(”threadPoolTaskExecutor“) 可以配置系统线程池异常捕获

事实上 afterExecute 并不会总是抛出异常 Throwable t,通过查看源码得知,异常是封装在此时的Future对象中的, 而此 Future 对象其实是一个 java.util.concurrent.FutureTask 的实现,默认的 run 方法其实调用的 java.util.concurrent.FutureTask.Sync.innerRun()。

吃掉了异常,将异常存储在java.util.concurrent.FutureTask.Sync的exception字段中

当我们获取异步执行的结果时, java.util.concurrent.FutureTask.get()

异常就会被包装成ExecutionException异常抛出。

ScheduledThreadPoolExecutor

java.util.concurrent.ScheduledThreadPoolExecutor 是继承 ThreadPoolExecutor 的,因此情况类似。

主线程和子线程

如果主线程想拿到子线程的异常,比如展示给界面,该怎么做?

  • 友好的做法是子线程不抛出异常,返回不同的结果,或者将异常封装到return对象中。父对象根据此结果/异常封装友好的提示给界面。

文章作者: shone
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 shone !
评论
 上一篇
String 的不可变性怎么理解? String 的不可变性怎么理解?
String 类String 类结构public final class String implements java.io.Serializable, Comparable<String>, CharSequence &
2023-06-25 shone
下一篇 
Java 多线程并发摘要 Java 多线程并发摘要
参考文档 一文秒懂系列之 Java 并发编程面试题 线程和执行器Thread 和 RunnableJava 从 JDK1.0 开始执行线程,在开始一个新的线程之前,你必须指定由这个线程执行的代码,通常称为 task 。 Java 中创建
2023-02-02
  目录