“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第5篇文章,点击查看活动详情”
力学如力耕,勤惰尔自知。上篇文章为大家详细介绍了下Dubbo提供的几种线程模型,在执行具体的逻辑的时候,当然不能每次都创建新的线程了,Dubbo其实也提供了几种线程池用来执行异步任务,今天,就为大家详细介绍下Dubbo线程池的实现。
一、温故知新
前文提到过,当前线程模型是AllDispatcher时候,会初始化AllChannelHandler对象。
public class AllDispatcher implements Dispatcher {
public static final String NAME = "all";
@Override
public ChannelHandler dispatch(ChannelHandler handler, URL url) {
return new AllChannelHandler(handler, url);
}
}
在AllChannelHandler对象内部,以connected方法为例,调用了父类的getExecutorService方法获取对应的线程池。
@Override
public void connected(Channel channel) throws RemotingException {
ExecutorService executor = getExecutorService();
executor.execute(new ChannelEventRunnable(channel, handler, ChannelState.CONNECTED));
}
查看WrappedChannelHandler-getExecutorService发现,实际调用的getSharedExecutorService方法,该方法内部通过调用getSharedExecutorService方法获取对应的线程池实例。
@Deprecated
public ExecutorService getExecutorService() {
return getSharedExecutorService();
}
public ExecutorService getSharedExecutorService() {
ExecutorRepository executorRepository =
ExtensionLoader.getExtensionLoader(ExecutorRepository.class).getDefaultExtension();
ExecutorService executor = executorRepository.getExecutor(url);
if (executor == null) {
executor = executorRepository.createExecutorIfAbsent(url);
}
return executor;
}
查看DefaultExecutorRepository-createExecutorIfAbsent方法发现,内部是调用了createExecutor方法创建了ExecutorService。下面这段代码想必大家都很熟悉了,Dubbo内部随处可见的,加载SPI实现的经典代码。
private ExecutorService createExecutor(URL url) {
return (ExecutorService) ExtensionLoader.getExtensionLoader(ThreadPool.class).getAdaptiveExtension().getExecutor(url);
}
查看ThreadPool源码如下,可以看到确实是个SPI扩展点,默认实现为“fixed”。
@SPI("fixed")
public interface ThreadPool {
@Adaptive({THREADPOOL_KEY})
Executor getExecutor(URL url);
}
查看ThreadPool实现如下,Dubbo提供了多种线程池实现
- FixedThreadPool
- 创建一个固线程个数的线程池
- LimitedThreadPool
- 创建一个线程池,线程数会动态的增加,但不超过阈值,空闲线程也不会回收
- EagerThreadPool
- 创建一个线程池,当核心线程没达到阈值时,将创建新的线程来执行任务,而不是先把任务放入队列。
- CachedThreadPool
- 自适应线程池,空闲1分钟,线程会被收回。有新任务过来时,会重新创建。
二、详细实现
2.1. FixedThreadPool
public class FixedThreadPool implements ThreadPool {
@Override
public Executor getExecutor(URL url) {
String name = url.getParameter(THREAD_NAME_KEY, DEFAULT_THREAD_NAME);
int threads = url.getParameter(THREADS_KEY, DEFAULT_THREADS);
int queues = url.getParameter(QUEUES_KEY, 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 NamedInternalThreadFactory(name, true), new AbortPolicyWithReport(name, url));
}
}
- 获取线程池名称,默认是dubbo
- 获取线程池中线程的数量
- 获取阻塞队列的大小
- 数量为0时,使用SynchronousQueue
- 数量小于0的时候,用无界阻塞队列LinkedBlockingQueue
- 数量大于0的时候,用有界阻塞队列LinkedBlockingQueue
- 使用juc下ThreadPoolExecutor创建线程池
- 线程拒绝策略选择了AbortPolicyWithReport,表示线程池队列已经并且没有空闲线程时,新的任务会被抛弃,并抛出异常
2.2. LimitedThreadPool
public class LimitedThreadPool implements ThreadPool {
@Override
public Executor getExecutor(URL url) {
String name = url.getParameter(THREAD_NAME_KEY, DEFAULT_THREAD_NAME);
int cores = url.getParameter(CORE_THREADS_KEY, DEFAULT_CORE_THREADS);
int threads = url.getParameter(THREADS_KEY, DEFAULT_THREADS);
int queues = url.getParameter(QUEUES_KEY, 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 NamedInternalThreadFactory(name, true), new AbortPolicyWithReport(name, url));
}
}
- 获取线程池名称,默认是dubbo
- 获取线程池中核心线程线程的数量
- 获取线程池中最大的线程的个数,默认是200
- 获取线程线程池阻塞队列的大小
- 使用juc下ThreadPoolExecutor创建线程池
- 创建线程池的以后设置keepAliveTime为 Long.MAX_VALUE,则线程用不过期被回收
- 拒绝策略依然是AbortPolicyWithReport
2.3. EagerThreadPool
由于juc里的线程池,采用的策略都是核心线程达到coreSize时未达到maxSize时,新来的任务会放入到队列里,队列满了后,才会继续创建线程直到数量 = maxSize。其实线程达到coreSize后,此刻线程池可能还有部分资源预期没被使用呢(假设coreSize != maxSize), 与其直接放入队列,不如继续创建线程,用来处理请求,从而提升系统性能,EagerThreadPool采用的就是后者,查看其源码实现如下:
public class EagerThreadPool implements ThreadPool {
@Override
public Executor getExecutor(URL url) {
String name = url.getParameter(THREAD_NAME_KEY, DEFAULT_THREAD_NAME);
int cores = url.getParameter(CORE_THREADS_KEY, DEFAULT_CORE_THREADS);
int threads = url.getParameter(THREADS_KEY, Integer.MAX_VALUE);
int queues = url.getParameter(QUEUES_KEY, DEFAULT_QUEUES);
int alive = url.getParameter(ALIVE_KEY, DEFAULT_ALIVE);
TaskQueue<Runnable> taskQueue = new TaskQueue<Runnable>(queues <= 0 ? 1 : queues);
EagerThreadPoolExecutor executor = new EagerThreadPoolExecutor(cores,
threads,
alive,
TimeUnit.MILLISECONDS,
taskQueue,
new NamedInternalThreadFactory(name, true),
new AbortPolicyWithReport(name, url));
taskQueue.setExecutor(executor);
return executor;
}
}
查看EagerThreadPoolExecutor源码如下;
public class EagerThreadPoolExecutor extends ThreadPoolExecutor {
private final AtomicInteger submittedTaskCount = new AtomicInteger(0);
... ...
@Override
public void execute(Runnable command) {
if (command == null) {
throw new NullPointerException();
}
submittedTaskCount.incrementAndGet();
... ...
super.execute(command);
}
}
可以看到,最终执行线程依然调用的是ThreadPoolExecutor的execute方法,查看其实现:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 步骤1:当前线程池个数是否小于corePoolSize,小于就开启新的线程
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// 步骤2:否则就入队列
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 步骤3:队列满了,则新增线程,新增失败执行拒绝策略
else if (!addWorker(command, false))
reject(command);
}
正常来说,当发现线程个数大于corePoolSize后,就要将任务入队列,但是EagerThreadPoolExecutor使用了自己的队列taskQueue,查看其offer方法如下:
@Override
public boolean offer(Runnable runnable) {
if (executor == null) {
throw new RejectedExecutionException("The task queue does not have executor!");
}
int currentPoolThreadSize = executor.getPoolSize();
// 当前任务数量 < 当前线程数,表示线程池处理能力绰绰有余,走走jdk的默认逻辑
if (executor.getSubmittedTaskCount() < currentPoolThreadSize) {
return super.offer(runnable);
}
// 当前线程池处理不过来了,但是距离maxSize还有一些距离,这里返回fase
// exceute方法则执行步骤3,尝试新增线程进行处理
if (currentPoolThreadSize < executor.getMaximumPoolSize()) {
return false;
}
// currentPoolThreadSize >= maxSize后,则走jdk的默认逻辑
return super.offer(runnable);
}
2.4. CachedThreadPool
public class CachedThreadPool implements ThreadPool {
@Override
public Executor getExecutor(URL url) {
String name = url.getParameter(THREAD_NAME_KEY, DEFAULT_THREAD_NAME);
int cores = url.getParameter(CORE_THREADS_KEY, DEFAULT_CORE_THREADS);
int threads = url.getParameter(THREADS_KEY, Integer.MAX_VALUE);
int queues = url.getParameter(QUEUES_KEY, DEFAULT_QUEUES);
int alive = url.getParameter(ALIVE_KEY, 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));
}
}
查看CachedThreadPool源码发现,通过设置线程keepAlive方式来实现线程的定时回收。
三、小节
本文主要为大家介绍了Dubbo的几种线程池实现,底层其实都是依赖juc的ThreadPoolExecutor,提供了多种默认实现,如果你觉得以上几种实现不符合你的业务场景,依然可以自己通过扩展SPI的方式,替换成自己的实现,个人觉得EagerThreadPool的实现对大家来说会有一些参考意义,不过创建线程的时候,如果设置coreSize = maxSize,效果其实和EagerThreadPool是一样的(手动🐶)你觉得呢?