百度的网站名,柯桥做网站,做网站的知名公司,顺的网站建设教程在日常的开发工作当中#xff0c;线程池往往承载着一个应用中最重要的业务逻辑#xff0c;因此我们有必要更多地去关注线程池的执行情况#xff0c;包括异常的处理和分析等。本文主要聚焦在如何正确使用线程池上#xff0c;以及提供一些实用的建议。文中会稍微涉及到一些线…在日常的开发工作当中线程池往往承载着一个应用中最重要的业务逻辑因此我们有必要更多地去关注线程池的执行情况包括异常的处理和分析等。本文主要聚焦在如何正确使用线程池上以及提供一些实用的建议。文中会稍微涉及到一些线程池实现原理方面的知识但是不会过多展开。线程池的异常处理UncaughtExceptionHandler我们都知道Runnable接口中的run方法是不允许抛出异常的因此派生出这个线程的主线程可能无法直接获得该线程在执行过程中的异常信息。如下例为什么会这样呢其实我们看一下Thread中的源码就会发现Thread在执行过程中如果遇到了异常会先判断当前线程是否有设置UncaughtExceptionHandler如果没有则会从线程所在的ThreadGroup中获取。注意每个线程都有自己的ThreadGroup即使你没有指定并且它实现了UncaughtExceptionHandler接口。我们看下ThreadGroup中默认的对UncaughtExceptionHandler接口的实现这个ThreadGroup如果有父ThreadGroup则调用父ThreadGroup的uncaughtException否则调用全局默认的Thread.DefaultUncaughtExceptionHandler如果全局的handler也没有设置则只是简单地将异常信息定位到System.err中这就是为什么我们应当在创建线程的时候去实现它的UncaughtExceptionHandler接口的原因这么做可以让你更方便地去排查问题。通过execute提交任务给线程池回到线程池这个话题如果我们向线程池提交的任务中没有对异常进行try...catch处理并且运行的时候出现了异常那会对线程池造成什么影响呢答案是没有影响线程池依旧可以正常工作但是异常却被吞掉了。这通常来说不是一个好事情因为我们需要拿到原始的异常对象去分析问题。那么怎样才能拿到原始的异常对象呢我们从线程池的源码着手开始研究这个问题。当然网上关于线程池的源码解析文章有很多这里限于篇幅直接给出最相关的部分代码这个方法就是真正去执行提交给线程池的任务的代码。这里我们略去其中不相关的逻辑重点关注第19行到第32行的逻辑其中第23行是真正开始执行提交给线程池的任务那么第20行是干什么的呢其实就是在执行提交给线程池的任务之前可以做一些前置工作同样的我们看到第31行这个是在执行完提交的任务之后可以做一些后置工作。beforeExecute这个我们暂且不管重点关注下afterExecute这个方法。我们可以看到在执行任务过程中一旦抛出任何类型的异常都会提交给afterExecute这个方法然而查看线程池的源代码我们可以发现默认的afterExecute是个空实现因此我们有必要继承ThreadPoolExecutor去实现这个afterExecute方法。看源码我们可以发现这个afterExecute方法是protected类型的从官方注释上也可以看到这个方法就是推荐子类去实现的。当然这个方法不能随意去实现需要遵循一定的步骤具体的官方注释也有讲这里摘抄如下那么通过这种方式就可以将原先可能被线程池吞掉的异常成功捕获到从而便于排查问题。但是这里还有个小问题我们注意到在runWorker方法中执行task.run();语句之后各种类型的异常都被抛出了那这些被抛出的异常去了哪里事实上这里的异常对象最终会被传入到Thread的dispatchUncaughtException方法中源码如下可以看到它会去获取UncaughtExceptionHandler的实现类然后调用其中的uncaughtException方法这也就回到了我们上一小节所分析的UncaughtExceptionHandler实现的具体逻辑。那么为了拿到最原始的异常对象除了实现UncaughtExceptionHandler接口之外也可以考虑实现afterExecute方法。通过submit提交任务到线程池这个同样很简单我们还是先回到submit方法的源码这里的execute方法调用的是ThreadPoolExecutor中的execute方法执行逻辑跟通过execute提交任务到线程池是一样的。我们先重点关注这里的newTaskFor方法其源码如下可以看到提交的Callable对象用FutureTask封装起来了。我们知道最终会执行到上述runWorker这个方法中并且最核心的执行逻辑就是task.run();这行代码。我们知道这里的task其实是FutureTask类型因此我们有必要看一下FutureTask中的run方法的实现可以看到这其中跟异常相关的最关键的代码就在第17行也就是setException(ex);这个地方。我们看一下这个地方的实现这里最关键的地方就是将异常对象赋值给了outcomeoutcome是FutureTask中的成员变量我们通过调用submit方法拿到一个Future对象之后再调用它的get方法其中最核心的方法就是report方法下面给出每个方法的源码首先是get方法可以看到最终调用了report方法其源码如下上面是一些状态判断如果当前任务不是正常执行完毕或者被取消的话那么这里的x其实就是原始的异常对象可以看到会被ExecutionException包装。因此在你调用get方法时可能会抛出ExecutionException异常那么调用它的getCause方法就可以拿到最原始的异常对象了。综上所述针对提交给线程池的任务可能会抛出异常这一问题主要有以下两种处理思路在提交的任务当中自行try...catch但这里有个不好的地方就是如果你会提交多种类型的任务到线程池中每种类型的任务都需要自行将异常try...catch住比较繁琐。而且如果你只是catch(Exception e)可能依然会漏掉一些包括Error类型的异常那为了保险起见可以考虑catch(Throwable t)。自行实现线程池的afterExecute方法或者实现Thread的UncaughtExceptionHandler接口。下面给出我个人创建线程池的一个示例供大家参考BlockingQueue queue new ArrayBlockingQueue(DEFAULT_QUEUE_SIZE); statisticsThreadPool new ThreadPoolExecutor(DEFAULT_CORE_POOL_SIZE, DEFAULT_MAX_POOL_SIZE, 60, TimeUnit.SECONDS, queue, new ThreadFactoryBuilder() .setThreadFactory(new ThreadFactory() { private int count 0; private String prefix StatisticsTask; Override public Thread newThread(Runnable r) { return new Thread(r, prefix - count); } }).setUncaughtExceptionHandler((t, e) - { String threadName t.getName(); logger.error(statisticsThreadPool error occurred! threadName: {}, error msg: {}