引言
ThreadPoolExcutor
是JDK
自带的线程池,也是我们在创建线程池时经常用到的创建方法。对线程池稍微有了解的同学都知道,线程池是一种典型的池化缓存设计。JDK
自带了四种任务拒绝策略,但是有时候是不能满足我们实际的业务需求的,所以此时我们需要自定义拒绝策略,来处理被线程池拒绝的任务。
- 自带线程池拒绝策略介绍
- 如何自定义拒绝策略
一、自带线程池拒绝策略介绍
JDK自带的线程池拒绝策略有如下四种:
1、 DiscardPolicy: 默默丢弃无法处理的任务,不予任何处理;
public static class DiscardPolicy implements RejectedExecutionHandler {
public DiscardPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
2、DiscardOldestPolicy: 丢弃队列中最老的任务, 尝试再次提交当前任务;
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public DiscardOldestPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
3、 AbortPolicy: 直接抛异常,阻止系统正常工作;
public static class AbortPolicy implements RejectedExecutionHandler {
public AbortPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
4、CallerRunsPolicy: 将任务分给调用线程来执行,运行当前被丢弃的任务,这样做不会真的丢弃任务,但是提交的线程性能有可能急剧下降
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
我们可以看得出来,前三种策略都是会丢弃原有的任务。但是在某些业务场景下,我们不能够粗暴的丢弃任务。第四种拒绝策略,是通过启动线程池的线程来处理丢弃的任务,但是问题是即便是线程池空闲,它也不会执行丢弃的任务,而是等待调用线程池的主线程来执行任务,直到任务结束。
二、自带线程池拒绝策略介绍
在线程池的定义中我们可以看到拒绝策略有个统一的实现接口,如下:
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
我们可以根据自己的业务需求来定义符合自己业务场景的处理策略。我们可以看下一些主流框架是如何自定义自己的处理策略的。
1、Netty 中的线程池拒绝策略
private static final class NewThreadRunsPolicy implements RejectedExecutionHandler {
NewThreadRunsPolicy() {
super();
}
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
try {
final Thread t = new Thread(r, "Temporary task executor");
t.start();
} catch (Throwable e) {
throw new RejectedExecutionException(
"Failed to start a new thread", e);
}
}
}
从上面的源码可以看出,Netty
的处理方式就是不丢弃任务,这个思想和CallerRunsPolicy
优点类似。只是在Netty
框架中的自定义拒绝策略中,是通过新建工作线程来完成被丢弃的任务的,但是我们看一看得出它在创建线程时,没有进行条件约束,只要资源允许就不断创建新的线程来进行处理。
2、Dubbo 中的线程池拒绝策略
public class AbortPolicyWithReport extends ThreadPoolExecutor.AbortPolicy {
protected static final Logger logger = LoggerFactory.getLogger(AbortPolicyWithReport.class);
private final String threadName;
private final URL url;
private static volatile long lastPrintTime = 0;
private static Semaphore guard = new Semaphore(1);
public AbortPolicyWithReport(String threadName, URL url) {
this.threadName = threadName;
this.url = url;
}
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
String msg = String.format("Thread pool is EXHAUSTED!" +
" Thread Name: %s, Pool Size: %d (active: %d, core: %d, max: %d, largest: %d), Task: %d (completed: %d)," +
" Executor status:(isShutdown:%s, isTerminated:%s, isTerminating:%s), in %s://%s:%d!",
threadName, e.getPoolSize(), e.getActiveCount(), e.getCorePoolSize(), e.getMaximumPoolSize(), e.getLargestPoolSize(),
e.getTaskCount(), e.getCompletedTaskCount(), e.isShutdown(), e.isTerminated(), e.isTerminating(),
url.getProtocol(), url.getIp(), url.getPort());
logger.warn(msg);
dumpJStack();
throw new RejectedExecutionException(msg);
}
private void dumpJStack() {
//省略实现
}
}
Dubbo
中的自定义拒绝策略中,打印了日志,输出当前线程的堆栈信息以及执行JDK
的默认拒绝策略。