11、Dubbo源码系列-几种线程池

398 阅读4分钟

“我报名参加金石计划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提供了多种线程池实现 image.png

  • 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是一样的(手动🐶)你觉得呢?