ThreadPoolExcutor是JDK自带的线程池,也是我们在创建线程池时经常用到的创建方法。线程池是一种典型的池化缓存设计。JDK自带了四种任务拒绝策略,但是有时候是不能满足我们实际的业务需求的,所以此时我们需要自定义拒绝策略,来处理被线程池拒绝的任务。
线程池(Thread Pool)是一种基于池化思想管理线程的工具,经常出现在多线程服务器中,如MySQL。
线程过多会带来额外的开销,其中包括创建销毁线程的开销、调度线程的开销等等,同时也降低了计算机的整体性能。线程池维护多个线程,等待监督管理者分配可并发执行的任务。这种做法,一方面避免了处理任务时创建销毁线程开销的代价,另一方面避免了线程数量膨胀导致的过分调度问题,保证了对内核的充分利用。
而本文描述线程池是JDK中提供的ThreadPoolExecutor类。
当然,使用线程池可以带来一系列好处:
线程池解决的核心问题就是资源管理问题。在并发环境下,系统不能够确定在任意时刻中,有多少任务需要执行,有多少资源需要投入。这种不确定性将带来以下若干问题:
为解决资源分配这个问题,线程池采用了“池化”(Pooling)思想。池化,顾名思义,是为了最大化收益并最小化风险,而将资源统一在一起管理的一种思想。
Pooling is the grouping together of resources (assets, equipment, personnel, effort, etc.) for the purposes of maximizing advantage or minimizing risk to the users. The term is used in finance, computing and equipment management.——wikipedia
“池化”思想不仅仅能应用在计算机领域,在金融、设备、人员管理、工作管理等领域也有相关的应用。
在计算机领域中的表现为:统一管理IT资源,包括服务器、存储、和网络资源等等。通过共享资源,使用户在低投入中获益。除去线程池,还有其他比较典型的几种使用策略包括:
如何自定义拒绝策略
原理:根据线程池的状态、任务队列来决定任务的丢弃或者是以某种方式处理。自定义也就是进一步处理过多的任务!
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
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);
}
dump的过程是现new线程池进行的。里面调用了JVMUtil.jstack(jStackStream)方法,该方法主要是jdk api 写文件的过程,这里不细说了。默认使在用户目录下生成文件,当然也可以在url配置dump.directory参数值。文件名比如Dubbo_JStack.log.2021-10-28_08/23/18
private void dumpJStack() {
long now = System.currentTimeMillis();
// dump every 10 minutes
if (now - lastPrintTime < TEN_MINUTES_MILLS) {
return;
}
// 信号量限制并发数为1
if (!guard.tryAcquire()) {
return;
}
ExecutorService pool = Executors.newSingleThreadExecutor();
pool.execute(() -> {
String dumpPath = url.getParameter(DUMP_DIRECTORY, System.getProperty("user.home"));
SimpleDateFormat sdf;
String os = System.getProperty(OS_NAME_KEY).toLowerCase();
// window system don't support ":" in file name
if (os.contains(OS_WIN_PREFIX)) {
sdf = new SimpleDateFormat(WIN_DATETIME_FORMAT);
} else {
sdf = new SimpleDateFormat(DEFAULT_DATETIME_FORMAT);
}
String dateStr = sdf.format(new Date());
// try-with-resources
try (FileOutputStream jStackStream = new FileOutputStream(
new File(dumpPath, "Dubbo_JStack.log" + "." + dateStr))) { // new File第二个参数是child及具体的文件名称
// 进去
JVMUtil.jstack(jStackStream);
} catch (Throwable t) {
logger.error("dump jStack error", t);
} finally {
// 释放信号量
guard.release();
}
lastPrintTime = System.currentTimeMillis();
});
// must shutdown thread pool ,if not will lead to OOM
pool.shutdown();
}
}
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
// 没有用到参数r
String msg = String.format("Thread pool is EXHAUSTED!" + // Thread pool is EXHAUSTED! 线程池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());
// 1.打日志 以AbortPolicyWithReportTest为例:Thread pool is EXHAUSTED! Thread Name: Test, Pool Size: 0 (active: 0, core: 1, max: 1, largest: 0), Task: 0 (completed: 0), Executor status:(isShutdown:false, isTerminated:false, isTerminating:false), in dubbo://10.20.130.230:20880!
logger.warn(msg);
// 2.dump系统当前线程的堆栈信息,进去
dumpJStack();
// 3.处理事件,事件监听模型的惯用法:发生事件后让对应的监听器进行处理,进去
dispatchThreadPoolExhaustedEvent(msg); // dispatch:派遣,发送;迅速处理,
// 4.最后还是抛异常,和AbortPolicy的rejectedExecution方法的内容一样,只是上面做了一些其他操作
throw new RejectedExecutionException(msg);
}
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(final Runnable r, final ThreadPoolExecutor executor) {
try {
executor.getQueue().offer(r, 60, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RejectedExecutionException("Interrupted waiting for BrokerService.worker");
}
throw new RejectedExecutionException("Timed Out while attempting to enqueue Task.");
}
});
public class RejectedExecutionHandlerChain implements RejectedExecutionHandler {
private final RejectedExecutionHandler[] handlerChain;
public static RejectedExecutionHandler build(List chain) {
Objects.requireNonNull(chain, "handlerChain must not be null");
RejectedExecutionHandler[] handlerChain = chain.toArray(new RejectedExecutionHandler[0]);
return new RejectedExecutionHandlerChain(handlerChain);
}
private RejectedExecutionHandlerChain(RejectedExecutionHandler[] handlerChain) {
this.handlerChain = Objects.requireNonNull(handlerChain, "handlerChain must not be null");
}
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
for (RejectedExecutionHandler rejectedExecutionHandler : handlerChain) {
rejectedExecutionHandler.rejectedExecution(r, executor);
}
}
}
定义了一个拒绝策略链,包装了一个拒绝策略列表,当触发拒绝策略时,会将策略链中的rejectedExecution依次执行一遍。
了解更多pinpointhttps://cnblogs.com/saneri/p/14450970.html