【开发小记】 Java 线程池 之 被“吃掉”的线程异常(附源码分析和解决方法)

本文探讨了一个Java线程池中任务异常未被捕获的问题,通过源码分析,揭示了线程池如何处理未捕获的异常。异常在执行任务的内部类中被try-catch,然后通过`UncaughtExceptionHandler`处理。解决方案包括在任务中添加try-catch、设置线程的`UncaughtExceptionHandler`或重写线程池的`afterExecute`方法。理解这些机制有助于更好地管理和处理线程池中的异常。
摘要由CSDN通过智能技术生成

前言

今天遇到了一个bug,现象是,一个任务放入线程池中,似乎“没有被执行”,日志也没有打。

经过本地代码调试之后,发现在任务逻辑的前半段,抛出了NPE,但是代码外层没有try-catch,导致这个异常被吃掉。

这个问题解决起来是很简单的,外层加个try-catch就好了,但是这个异常如果没有被catch,线程池内部逻辑是怎么处理这个异常的呢?这个异常最后会跑到哪里呢?

带着疑问和好奇心,我研究了一下线程池那一块的源码,并且做了以下的总结。

源码分析

项目中出问题的代码差不多就是下面这个样子

ExecutorService threadPool = Executors.newFixedThreadPool(3);

threadPool.submit(() -> {
   
    String pennyStr = null;
    Double penny = Double.valueOf(pennyStr);
    ...
})

先进到newFixedThreadPool这个工厂方法中看生成的具体实现类,发现是ThreadPoolExecutor

public static ExecutorService newFixedThreadPool(int nThreads) {
   
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

再看这个类的继承关系,

image

再进到submit方法,这个方法在ExecutorService接口中约定,其实是在AbstractExectorService中实现,ThreadPoolExecutor并没有override这个方法。

 public Future<?> submit(Runnable task) {
   
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }

protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
   
        return new FutureTask<T>(runnable, value);
    }

对应的FutureTask对象的构造方法

public FutureTask(Runnable runnable, V result) {
   
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // state由volatile 修饰 保证多线程下的可见性
    }

对应Callable 对象的构造方法

public static <T> Callable<T> callable(Runnable task, T result) {
   
        if (task == null)
            throw new NullPointerException();
        return new RunnableAdapter<T>(task, result);
    }

对应RunnableAdapter 对象的构造方法

 /**
     * A callable that runs given task and returns given result
     * 一个能执行所给任务并且返回结果的Callable对象
     */
    static final class RunnableAdapter<T> implements Callable<T> {
   
        final Runnable task;
        final T result;
        RunnableAdapter(Runnable task, T result) {
   
            this.task = task;
            this.result = result;
        }
        public T call() {
   
            task.run();
            return result;
        }
    }

总结上面的,newTaskFor就是把我们提交的Runnable 对象包装成了一个Fu

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值