之前线上项目偶发出现线程池耗尽的问题,最近终于有空能好好研究一把,问题实际并不复杂,也得益于Dubbo线程池的拒绝策略才能很快找到大致的原因。
通过这个问题,也有些好奇各家使用的线程池拒绝策略是怎样的,刨刨坑、挖挖土,一起来看看吧~
问题背景
之前线上偶发出现线程池耗尽问题,现象如下:
在调用下游Dubbo
接口时,提示Server
端的线程池耗尽。
最开始以为是有突发流量,但是监控显示流量稳定,并且扩容后发现问题依然存在,渐渐意识到问题并不简单。
问题分析
既然有异常日志和堆栈,先看看到底什么场景下会出现这个异常。在Dubbo
源码中,我们可以找到这一段提示出现在AbortPolicyWithReport
中。
public class AbortPolicyWithReport extends ThreadPoolExecutor.AbortPolicy
AbortPolicyWithReport
继承自 java.util.concurrent.ThreadPoolExecutor.AbortPolicy,是一种线程池拒绝策略,当线程池中的缓冲任务队列满,且线程数量达到最大时,就会触发拒绝策略,调用拒绝策略的rejectedExecution()
方法进行处理。
那么,有哪些不同的拒绝策略呢?
JDK线程池拒绝策略
在java.util.concurrent.ThreadPoolExecutor
,我们可以找到JDK预设置的四种拒绝策略:
- CallerRunsPolicy - 调用者线程处理
该策略下,如果线程池未关闭,则交由当前调用者线程进行处理,否则直接丢弃任务。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
- AbortPolicy - 抛出异常
如果不配置拒绝策略的话,线程池会默认使用该策略,直接抛出rejectedExecution
,交由上层业务处理。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("...");
}
- DiscardPolicy - 丢弃当前任务
最简单的处理方法,直接丢弃。
//实际方法体就是空的,即该场景下不处理,直接丢弃
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
- DiscardOldestPolicy - 丢弃下一个要执行的任务
该策略是丢弃队列中最老的任务(其实就是下一个要执行的任务),并尝试执行当前任务。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
Dubbo线程池拒绝策略
那么Dubbo
的拒绝策略是怎样的呢?
其实从名字就能看出来,AbortPolicyWithReport
。
public class AbortPolicyWithReport extends ThreadPoolExecutor.AbortPolicy {
...
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
String msg = String.format("Thread pool is EXHAUSTED!" + ...);
logger.warn(msg);
dumpJStack();
dispatchThreadPoolExhaustedEvent(msg);
throw new RejectedExecutionException(msg)