1. 原码、反码、补码的基本概念
在计算机系统中,数字都是以二进制的形式存储和运算的。为了表示有符号数(即正数和负数),计算机科学家们设计了三种编码方式:原码、反码和补码。这三种编码方式各有特点,但最终补码成为了现代计算机系统中表示有符号数的标准方式。
1# 1. 概述
本文分享 Dubbo 的线程池策略。在 《Dubbo 用户指南 —— 线程模型》 一文中,我们可以看到 Dubbo 提供了三种线程池的实现:
fixed固定大小线程池,启动时建立线程,不关闭,一直持有。(缺省)cached缓存线程池,空闲一分钟自动删除,需要时重建。limited可伸缩线程池,但池中的线程数只会增长不会收缩。只增长不收缩的目的是为了避免收缩时突然来了大流量引起的性能问题。eager优先创建Worker线程池。在任务数量大于corePoolSize但是小于maximumPoolSize时,优先创建Worker来处理任务。当任务数量大于maximumPoolSize时,将任务放入阻塞队列中。阻塞队列充满时抛出RejectedExecutionException。(相比于cached:cached在任务数量超过maximumPoolSize时直接抛出异常而不是将任务放入阻塞队列)
本文涉及的类,如下图所示:

2. ThreadPool
com.alibaba.dubbo.common.threadpool.ThreadPool ,线程池接口。代码如下:
java复制@SPI("fixed")
public interface ThreadPool {
/**
* 线程池
*
* @param url URL
* @return 线程池
*/
@Adaptive({Constants.THREADPOOL_KEY})
Executor getExecutor(URL url);
}
@SPI("fixed")注解,Dubbo SPI 拓展点,默认为"fixed"。@Adaptive({Constants.THREADPOOL_KEY})注解,基于 Dubbo SPI Adaptive 机制,加载对应的线程池实现,使用URL.threadpool属性。#getExecutor(url)方法,获得对应的线程池的执行器。
3. FixedThreadPool
com.alibaba.dubbo.common.threadpool.support.fixed.FixedThreadPool ,实现 ThreadPool 接口,固定大小线程池,启动时建立线程,不关闭,一直持有。代码如下:
java复制public class FixedThreadPool implements ThreadPool {
@Override
public Executor getExecutor(URL url) {
// 线程名
String name = url.getParameter(Constants.THREAD_NAME_KEY, Constants.DEFAULT_THREAD_NAME);
// 线程数
int threads = url.getParameter(Constants.THREADS_KEY, Constants.DEFAULT_THREADS);
// 队列数
int queues = url.getParameter(Constants.QUEUES_KEY, Constants.DEFAULT_QUEUES);
// 创建执行器
return new ThreadPoolExecutor(threads, threads, 0, TimeUnit.MILLISECONDS,
queues == 0 ? new SynchronousQueue<Runnable>() :
(queues < 0 ? new LinkedBlockingQueue<Runnable>()
: new LinkedBlockingQueue<Runnable>(queues)),
new NamedThreadFactory(name, true), new AbortPolicyWithReport(name, url));
}
}
- 第 5 行:线程名。优先从
URL.threadname配置,其次,使用"Dubbo"。 - 第 7 行:线程数。优先从
URL.threads配置,其次,使用200。 - 第 9 行:队列数。优先从
URL.queues配置,其次,使用0。queues == 0, SynchronousQueue 对象。queues < 0, LinkedBlockingQueue 对象。queues > 0,带队列数的 LinkedBlockingQueue 对象。
- 第 10 至 14 行:创建线程池执行器 ThreadPoolExecutor 对象。
java.util.concurrent.SynchronousQueue,不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态。java.util.concurrent.LinkedBlockingQueue,基于链表的阻塞队列。NamedThreadFactory,在 《精尽 Dubbo 源码分析 —— 项目结构一览》「4.4 common-util」 有详细解析。AbortPolicyWithReport,在 《精尽 Dubbo 源码分析 —— 项目结构一览》「4.4 common-util」 有详细解析。
4. CachedThreadPool
com.alibaba.dubbo.common.threadpool.support.cached.CachedThreadPool ,实现 ThreadPool 接口,缓存线程池,空闲一定时长,自动删除,需要时重建。代码如下:
java复制public class CachedThreadPool 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, Integer.MAX_VALUE);
// 队列数
int queues = url.getParameter(Constants.QUEUES_KEY, Constants.DEFAULT_QUEUES);
// 线程存活时长
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 NamedThreadFactory(name, true), new AbortPolicyWithReport(name, url));
}
}
- 和
FixedThreadPool实现思路一致,差异点在coresthreadsalive配置项。 cores配置项,核心线程数,优先从URL.corethreads配置,其次,使用0。threads配置项,最大线程数,优先从URL.threads配置,其次,使用Integer.MAX_VALUE。alive配置项,线程存活时长,优先从URL.alive配置,其次,使用60 * 1000毫秒。
5. LimitedThreadPool
com.alibaba.dubbo.common.threadpool.support.limited.LimitedThreadPool ,实现 ThreadPool 接口,可伸缩线程池,但池中的线程数只会增长不会收缩。只增长不收缩的目的是为了避免收缩时突然来了大流量引起的性能问题。代码如下:
java复制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);
// 创建执行器
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 NamedThreadFactory(name, true), new AbortPolicyWithReport(name, url));
}
}
- 和
FixedThreadPool实现思路一致,差异点在coresthreads配置项。 cores配置项,核心线程数,优先从URL.corethreads配置,其次,使用0。threads配置项,最大线程数,优先从URL.threads配置,其次,使用200。
6. EagerThreadPool
com.alibaba.dubbo.common.threadpool.support.eager.EagerThreadPool ,实现 ThreadPool 接口,优先创建Worker线程池。在任务数量大于corePoolSize但是小于maximumPoolSize时,优先创建Worker来处理任务。当任务数量大于maximumPoolSize时,将任务放入阻塞队列中。阻塞队列充满时抛出RejectedExecutionException。(相比于cached:cached在任务数量超过maximumPoolSize时直接抛出异常而不是将任务放入阻塞队列)
6.1 EagerThreadPool
java复制public class EagerThreadPool 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, Integer.MAX_VALUE);
// 队列数
int queues = url.getParameter(Constants.QUEUES_KEY, Constants.DEFAULT_QUEUES);
// 线程存活时长
int alive = url.getParameter(Constants.ALIVE_KEY, Constants.DEFAULT_ALIVE);
// 创建执行器
return new EagerThreadPoolExecutor(cores,
threads,
alive,
TimeUnit.MILLISECONDS,
queues == 0 ? new SynchronousQueue<Runnable>() :
(queues < 0 ? new LinkedBlockingQueue<Runnable>()
: new LinkedBlockingQueue<Runnable>(queues)),
new NamedThreadFactory(name, true),
new AbortPolicyWithReport(name, url));
}
}
- 和
FixedThreadPool实现思路一致,差异点在coresthreadsalive配置项。 cores配置项,核心线程数,优先从URL.corethreads配置,其次,使用0。threads配置项,最大线程数,优先从URL.threads配置,其次,使用Integer.MAX_VALUE。alive配置项,线程存活时长,优先从URL.alive配置,其次,使用60 * 1000毫秒。
6.2 EagerThreadPoolExecutor
com.alibaba.dubbo.common.threadpool.support.eager.EagerThreadPoolExecutor ,实现 ThreadPoolExecutor 类,支持优先创建Worker线程池。代码如下:
java复制public class EagerThreadPoolExecutor extends ThreadPoolExecutor {
/**
* 正在提交的任务数
*/
private final AtomicInteger submittedTaskCount = new AtomicInteger(0);
public EagerThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit, TaskQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
}
public int getSubmittedTaskCount() {
return submittedTaskCount.get();
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
submittedTaskCount.decrementAndGet();
}
@Override
public void execute(Runnable command) {
if (command == null) {
throw new NullPointerException();
}
// 提交任务数 + 1
submittedTaskCount.incrementAndGet();
try {
// 提交任务执行
super.execute(command);
} catch (RejectedExecutionException rx) {
// 发生拒绝提交时,尝试重新提交
final TaskQueue queue = (TaskQueue) super.getQueue();
try {
if (!queue.retryOffer(command, 0, TimeUnit.MILLISECONDS)) {
submittedTaskCount.decrementAndGet();
throw new RejectedExecutionException("Queue capacity is full.", rx);
}
} catch (InterruptedException x) {
submittedTaskCount.decrementAndGet();
throw new RejectedExecutionException(x);
}
} catch (Throwable t) {
submittedTaskCount.decrementAndGet();
throw t;
}
}
}
submittedTaskCount属性,正在提交的任务数。#execute(command)方法,提交任务执行。- 第 28 行:提交任务数 + 1 。
- 第 30 行:调用父类的方法,提交任务执行。
- 【发生拒绝提交时】第 32 至 41 行:发生拒绝提交时,尝试重新提交。
- 第 34 行:调用
TaskQueue#retryOffer(command, 0, TimeUnit.MILLISECONDS)方法,尝试提交到队列。详细解析,见 「6.3 TaskQueue」 。 - 第 35 行:提交任务数 - 1 。
- 第 36 行:提交失败,抛出 RejectedExecutionException 异常。
- 第 34 行:调用
- 【发生异常时】第 37 至 40 行:提交任务数 - 1 ,并抛出异常。
#afterExecute(r, t)方法,执行完成,提交任务数 - 1 。
6.3 TaskQueue
com.alibaba.dubbo.common.threadpool.support.eager.TaskQueue ,实现 java.util.concurrent.LinkedBlockingQueue 类,任务队列。代码如下:
java复制public class TaskQueue<R extends Runnable> extends LinkedBlockingQueue<Runnable> {
private static final long serialVersionUID = -2635853580887179627L;
private EagerThreadPoolExecutor executor;
public TaskQueue(int capacity) {
super(capacity);
}
public void setExecutor(EagerThreadPoolExecutor exec) {
executor = exec;
}
@Override
public boolean offer(Runnable runnable) {
if (executor == null) {
throw new RejectedExecutionException("The task queue does not have executor!");
}
// 当前线程数
int currentPoolThreadSize = executor.getPoolSize();
// 有线程空闲,直接添加到队列
if (executor.getSubmittedTaskCount() < currentPoolThreadSize) {
return super.offer(runnable);
}
// 当前线程数小于最大线程数,返回 false ,让线程池去创建新的线程
if (currentPoolThreadSize < executor.getMaximumPoolSize()) {
return false;
}
// 添加到队列
return super.offer(runnable);
}
/**
* 重试提交任务到队列
*
* @param o 任务
* @param timeout 超时时间
* @param unit 时间单位
* @return 是否提交成功
* @throws InterruptedException 当发生中断时
*/
public boolean retryOffer(Runnable o, long timeout, TimeUnit unit) throws InterruptedException {
if (executor.isShutdown()) {
throw new RejectedExecutionException("Executor is shutdown!");
}
return super.offer(o, timeout, unit);
}
}
executor属性,线程池。#offer(runnable)方法,代码如下:- 第 19 至 21 行:若线程池未初始化,抛出 RejectedExecutionException 异常。
- 第 23 至 25 行:若有线程空闲,直接添加到队列。因为有空闲的线程,可以让他们去拉取任务执行。
- 第 27 至 29 行:若当前线程数小于最大线程数,返回
false,让线程池去创建新的线程。这样,每次提交任务时,当线程池的线程数小于maximumPoolSize时,优先创建线程,而不是提交到队列。 - 第 31 行:添加到队列。
#retryOffer(o, timeout, unit)方法,重试提交任务到队列。
