写在前面
dubbo目前提供了三种线程池的实现,如下:
fixed:固定大小的线程池,启动时建立,并且不会关闭,这也是缺省的设置。
cached:缓存线程池,空闲一分钟自动删除,需要时重建。
limited:可伸缩线程池,但是线程池中的线程数只会增长不会收缩,这样做的目的是为了避免当进行收缩时流量突然增加造成性能问题。
在dubbo源码中相关代码在dubbo-common
模块,如下图:
图中的ThreadPool
就是顶层的接口,我们就从这个接口开始吧!
1:ThreadPool
源码如下:
@SPI("fixed")
public interface ThreadPool {
// public static final String THREADPOOL_KEY = "threadpool";
@Adaptive({Constants.THREADPOOL_KEY})
Executor getExecutor(URL url);
}
从接口中我们可以得到以下的信息:
@SPI("fixed"):是一个Dubbo SPI接口,并且默认使用名称为fixed的扩展实现类。
@Adaptive({Constants.THREADPOOL_KEY}):是一个自适应扩展类,并且使用public static final String THREADPOOL_KEY = "threadpool";作为key从URL上寻找值作为目标扩展类的名称。
关于自适应扩展类可以参考dubbo学习系列 中的"dubbo之@Adaptive注解分析"这篇文章。
类图如下:
下面我们从FixedThreadPool
子类开始分析。
2:FixedThreadPool
固定大小线程数的线程池实现,启动时直接初始化,不关闭,一直持有,源码如下:
// 创建一个复用固定线程数的线程池,可参考jdk的API方法java.util.concurrent.Executors#newFixedThreadPool(int)
public class FixedThreadPool implements ThreadPool {
@Override
public Executor getExecutor(URL url) {
// public static final String THREAD_NAME_KEY = "threadname";
// public static final String DEFAULT_THREAD_NAME = "Dubbo";
// 从URL上获取线程名称信息
String name = url.getParameter(Constants.THREAD_NAME_KEY, Constants.DEFAULT_THREAD_NAME);
// public static final String THREADS_KEY = "threads";
// public static final int DEFAULT_THREADS = 200;
// 从URL上获取线程数
int threads = url.getParameter(Constants.THREADS_KEY, Constants.DEFAULT_THREADS);
// public static final String QUEUES_KEY = "queues";
// public static final int DEFAULT_QUEUES = 0;
// 从URL上获取任务队列大小
int queues = url.getParameter(Constants.QUEUES_KEY, Constants.DEFAULT_QUEUES);
// 2022-01-20 20:30:36
return new ThreadPoolExecutor(threads, threads, 0, TimeUnit.MILLISECONDS,
queues == 0 ? new SynchronousQueue<Runnable>() :
(queues < 0 ? new LinkedBlockingQueue<Runnable>()
: new LinkedBlockingQueue<Runnable>(queues)),
new NamedInternalThreadFactory(name, true), new AbortPolicyWithReport(name, url));
}
}
2022-01-20 20:30:36
处是使用java.util.concurrent.ThreadPoolExecutor创建线程池,NamedInternalThreadFactory
是定义自定义名称的线程池工厂,AbortPolicyWithReport
是自定义拒绝策略。
3:CachedThreadPool
缓存线程池,带有一定的空闲时长,超过空闲时长则删除,需要时再创建,源码如下:
// 该线程池实现类可自动调整线程数。默认超过一分钟线程被回收,如果请求数增加会创建新线程。
// 具体可以参看:java.util.concurrent.Executors#newCachedThreadPool()
public class CachedThreadPool implements ThreadPool {
@Override
public Executor getExecutor(URL url) {
// 线程的名称
String name = url.getParameter(Constants.THREAD_NAME_KEY, Constants.DEFAULT_THREAD_NAME);
// public static final String CORE_THREADS_KEY = "corethreads";
// public static final int DEFAULT_CORE_THREADS = 0;
// 核心线程数,默认0个
int cores = url.getParameter(Constants.CORE_THREADS_KEY, Constants.DEFAULT_CORE_THREADS);
// public static final String THREADS_KEY = "threads";
// @Native public static final int MAX_VALUE = 0x7fffffff;
// 核心线程数,默认0x7fffffff
int threads = url.getParameter(Constants.THREADS_KEY, Integer.MAX_VALUE);
// public static final String QUEUES_KEY = "queues";
// public static final int DEFAULT_QUEUES = 0;
// 任务队列大小,默认为0
int queues = url.getParameter(Constants.QUEUES_KEY, Constants.DEFAULT_QUEUES);
// public static final String ALIVE_KEY = "alive";
// public static final int DEFAULT_ALIVE = 60 * 1000;
// 回收前空闲时长,默认为1分钟
int alive = url.getParameter(Constants.ALIVE_KEY, Constants.DEFAULT_ALIVE);
return new ThreadPoolExecutor(cores, threads, alive, TimeUnit.MILLISECONDS,
queues == 0 ? new SynchronousQueue<Runnable>() :
(queues < 0 ? new LinkedBlockingQueue<Runnable>()
: new LinkedBlockingQueue<Runnable>(queues)),
new NamedInternalThreadFactory(name, true), new AbortPolicyWithReport(name, url));
}
}
4:LimitedThreadPool
只增长不收缩的线程池,这么做的目的是避免进行收缩时突然来了大流量造成性能问题。
public class LimitedThreadPool implements ThreadPool {
@Override
public Executor getExecutor(URL url) {
String name = url.getParameter(Constants.THREAD_NAME_KEY, Constants.DEFAULT_THREAD_NAME);
int cores = url.getParameter(Constants.CORE_THREADS_KEY, Constants.DEFAULT_CORE_THREADS);
int threads = url.getParameter(Constants.THREADS_KEY, Constants.DEFAULT_THREADS);
int queues = url.getParameter(Constants.QUEUES_KEY, Constants.DEFAULT_QUEUES);
// 通过设置过期时间为Long.MAX_VALUE保证永不过期
return new ThreadPoolExecutor(cores, threads, Long.MAX_VALUE, TimeUnit.MILLISECONDS,
queues == 0 ? new SynchronousQueue<Runnable>() :
(queues < 0 ? new LinkedBlockingQueue<Runnable>()
: new LinkedBlockingQueue<Runnable>(queues)),
new NamedInternalThreadFactory(name, true), new AbortPolicyWithReport(name, url));
}
}
该类和3:CachedThreadPool
的区别就是超时时长设置的是一个超大值Long.MAX_VALUE,可以认为就是不超时。我们可以看到所有的线程池实现都使用了同一个拒绝策略AbortPolicyWithReport
,具体参考5:AbortPolicyWithReport
。
5:AbortPolicyWithReport
可通过如下代码debug拒绝策略代码的执行:
public class MyMain {
public static void main(String[] args) {
URL url = URL.valueOf("dubbo://192.168.0.101:20880?fruit.granter=banana");
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
5,
5,
Long.MAX_VALUE,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(5),
new NamedInternalThreadFactory("dongshidaddy", true),
new AbortPolicyWithReport("dongshidaddy", url));
for (int i = 0; i < 100; i++) {
threadPoolExecutor.execute(() -> {
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
}
dubbo自定义的拒绝策略,当无可用线程,并且任务队列也满时会通过该类来执行拒绝策略,主要实现的是打印线程信息,定位问题。
源码如下:
class FakeCls {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
// 2022-01-21 10:57:10
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);
// 2022-01-21 11:01:38
dumpJStack();
// 抛出运行时异常java.util.concurrent.RejectedExecutionException
throw new RejectedExecutionException(msg);
}
}
2022-01-21 10:57:10
生成相关的线程和任务运行信息,如Thread pool is EXHAUSTED! Thread Name: dongshidaddy, Pool Size: 5 (active: 5, core: 5, max: 5, largest: 5), Task: 10 (completed: 0), Executor status:(isShutdown:false, isTerminated:false, isTerminating:false), in dubbo://192.168.0.101:20880!
。2022-01-21 11:01:38
处是dump转存线程栈信息,具体参考5.1:dumpJStack
。
5.1:dumpJStack
源码如下:
class FakeCls {
private void dumpJStack() {
long now = System.currentTimeMillis();
// 考虑到避免影响系统性能,十分钟内只转存一次
if (now - lastPrintTime < 10 * 60 * 1000) {
return;
}
// private static Semaphore guard = new Semaphore(1);
// 值为1的信号量,即只允许一个线程进入,获取不到直接return返回
if (!guard.tryAcquire()) {
return;
}
// 启动一个线程来执行线程信息的准村
Executors.newSingleThreadExecutor().execute(new Runnable() {
@Override
public void run() {
// 转存路径:C:\Users\Administrator
String dumpPath = url.getParameter(Constants.DUMP_DIRECTORY, System.getProperty("user.home"));
SimpleDateFormat sdf;
// 操作系统:windows 10
String OS = System.getProperty("os.name").toLowerCase();
// 这里特殊处理的原因是,win系统不支持文件名称带有:
if(OS.contains("win")){
sdf = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
} else {
sdf = new SimpleDateFormat("yyyy-MM-dd_HH:mm:ss");
}
String dateStr = sdf.format(new Date());
FileOutputStream jstackStream = null;
try {
// 创建文件的输出流,用于写信息
// C:\Users\Administrator Dubbo_JStack.log.2022-01-21_11-10-27
jstackStream = new FileOutputStream(new File(dumpPath, "Dubbo_JStack.log" + "." + dateStr));
// 2022-01-21 11:12:35
JVMUtil.jstack(jstackStream);
} catch (Throwable t) {
logger.error("dump jstack error", t);
} finally {
// 释放信号量,以便其他线程acquire
guard.release();
// 关闭流
if (jstackStream != null) {
try {
jstackStream.flush();
jstackStream.close();
} catch (IOException e) {
}
}
}
lastPrintTime = System.currentTimeMillis();
}
});
}
}
2022-01-21 11:12:35
处是写信息到文件中,具体参考5.2:JVMUtil.jstack
。
5.2:JVMUtil.jstack
源码如下:
class FakeCls {
public class JVMUtil {
public static void jstack(OutputStream stream) throws Exception {
ThreadMXBean threadMxBean = ManagementFactory.getThreadMXBean();
for (ThreadInfo threadInfo : threadMxBean.dumpAllThreads(true, true)) {
stream.write(getThreadDumpString(threadInfo).getBytes());
}
}
private static String getThreadDumpString(ThreadInfo threadInfo) {
StringBuilder sb = new StringBuilder("\"" + threadInfo.getThreadName() + "\"" +
" Id=" + threadInfo.getThreadId() + " " +
threadInfo.getThreadState());
if (threadInfo.getLockName() != null) {
sb.append(" on " + threadInfo.getLockName());
}
if (threadInfo.getLockOwnerName() != null) {
sb.append(" owned by \"" + threadInfo.getLockOwnerName() +
"\" Id=" + threadInfo.getLockOwnerId());
}
if (threadInfo.isSuspended()) {
sb.append(" (suspended)");
}
if (threadInfo.isInNative()) {
sb.append(" (in native)");
}
sb.append('\n');
int i = 0;
StackTraceElement[] stackTrace = threadInfo.getStackTrace();
MonitorInfo[] lockedMonitors = threadInfo.getLockedMonitors();
for (; i < stackTrace.length && i < 32; i++) {
StackTraceElement ste = stackTrace[i];
sb.append("\tat " + ste.toString());
sb.append('\n');
if (i == 0 && threadInfo.getLockInfo() != null) {
Thread.State ts = threadInfo.getThreadState();
switch (ts) {
case BLOCKED:
sb.append("\t- blocked on " + threadInfo.getLockInfo());
sb.append('\n');
break;
case WAITING:
sb.append("\t- waiting on " + threadInfo.getLockInfo());
sb.append('\n');
break;
case TIMED_WAITING:
sb.append("\t- waiting on " + threadInfo.getLockInfo());
sb.append('\n');
break;
default:
}
}
for (MonitorInfo mi : lockedMonitors) {
if (mi.getLockedStackDepth() == i) {
sb.append("\t- locked " + mi);
sb.append('\n');
}
}
}
if (i < stackTrace.length) {
sb.append("\t...");
sb.append('\n');
}
LockInfo[] locks = threadInfo.getLockedSynchronizers();
if (locks.length > 0) {
sb.append("\n\tNumber of locked synchronizers = " + locks.length);
sb.append('\n');
for (LockInfo li : locks) {
sb.append("\t- " + li);
sb.append('\n');
}
}
sb.append('\n');
return sb.toString();
}
}
}
如下是生成的转存文件和其部分内容: