线程池的拒绝策略
作者:互联网
-
为什么要设计池
抵消每次获取资源产生的消耗,这种池的设计会初始预设资源,可类比食堂打饭,类似的池设计有线程池、jdbc连接池等,跟池有关的特征包括:池子的初始值、最大值、活跃值等 -
线程池什么时候触发拒绝策略
一般是超过池的最大值时触发,但线程连接池还有一个阻塞队列缓冲区,当前提交任务数大于(maxPoolSize + queueCapacity)时就会触发线程池的拒绝策略了。 -
线程池有哪几种拒绝策略
一般线程池都是通过RejectedExecutionHandler接口中的rejectedExecution方法实现拒绝策略,一般有四种拒绝策略,分别对应不用方法策略
- CallerRunPolicy(调用者策略)
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
当线程池满了后,此拒绝策略会把线程丢给调用者自己处理,适用于对时间要求不高,但是不允许失败的场景
2. 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());
}
}
线程池的默认拒绝策略,使用任何场景,但是使用时应注意处理好抛出的异常,可能会打断当前执行
3. DiscardPolicy
public static class DiscardPolicy implements RejectedExecutionHandler {
public DiscardPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
没使用,就是超出了就不管
4. 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);
}
}
}
把最先进入队列的线程抛弃,这个策略使用于消息更新,永远是最新的消息最需要
- 除了java线程池的拒绝策略,还有哪些其他的拒绝策略
一些开源的框架或者软件也有很好的拒绝策略
比如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的工作线程触发了线程拒绝后,主要做了三个事情,原则就是尽量让使用者清楚触发线程拒绝策略的真实原因
- 输出了一条警告级别的日志,日志内容为线程池的详细设置参数,以及线程池当前的状态,还有当前拒绝任务的一些详细信息。可以说,这条日志,使用dubbo的有过生产运维经验的或多或少是见过的,这个日志简直就是日志打印的典范,其他的日志打印的典范还有spring。得益于这么详细的日志,可以很容易定位到问题所在
- 输出当前线程堆栈详情,这个太有用了,当你通过上面的日志信息还不能定位问题时,案发现场的dump线程上下文信息就是你发现问题的救命稻草,这个可以参考《dubbo线程池耗尽事件-"CyclicBarrier惹的祸"》
- 继续抛出拒绝执行异常,使本次任务失败,这个继承了JDK默认拒绝策略的特性
标签:策略,拒绝,static,线程,日志,public 来源: https://www.cnblogs.com/thewei/p/15781166.html