Apache Dubbo系列:Dubbo的线程池策略

概述

我们上篇文章讲到,Dubbo提供了多种线程模型,是为了尽早的释放I/O线程,把耗时的业务分派给业务线程池,那么这里的线程池是什么类型的线程池呢?是我们所熟知的JAVA线程池吗?并不是,这里的线程池,也是Dubbo的扩展接口ThreadPool。

 

ThreadPool

Dubbo为我们提供了四种类型的线程池,分别是

1、FixedThreadPool(默认),固定线程数的线程池。

2、LimitedThreadPool,线程池的线程数随着并发量动态增加,但不会超过配置的阈值。
3、EagerThreadPool,当提交的任务超过线程池的配置而触发拒绝策略后,会再次将任务投入队列。

4、CachedThreadPool,如果线程池里的线程空闲一定时间后,会回收线程。

 

源码分析

要了解Dubbo为我们提供了哪些线程池,我们首先要看一看ThreadPool这个扩展点接口的代码


@SPI("fixed")
public interface ThreadPool {
    @Adaptive({Constants.THREADPOOL_KEY})
    Executor getExecutor(URL url);
}

可以看到,该接口上的SPI注解,表明该方法是一个扩展点接口。该接口只有一个方法,getExecutor,用来获取线程池,返回Executor的实例,而这个Executor是JAVA并发包下的java.util.concurrent.Executor。

下面我们一一分析每种类型线程池的源码

FixedThreadPool类,固定数量的线程池:


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 NamedInternalThreadFactory(name, true), new AbortPolicyWithReport(name, url));
    }
}

源码很简单,通过URL参数获取创建线程池的各种参数,包括线程名字,线程数量和等待队列大小,然后通过JAVA并发包下的java.util.concurrent.ThreadPoolExecutor创建线程池。注意,线程池的corePoolSize和maximumPoolSize都是一样的哦。

 

LimitedThreadPool类:


public class LimitedThreadPool implements ThreadPool {
    @Override
    public Executor getExecutor(URL url) {
        // 获取线程名
        String name = url.getParameter(Constants.THREAD_NAME_KEY, Constants.DEFAULT_THREAD_NAME);
        // 获取corePoolSize
        int cores = url.getParameter(Constants.CORE_THREADS_KEY, Constants.DEFAULT_CORE_THREADS);
        // 获取maximumPoolSize
        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 NamedInternalThreadFactory(name, true), new AbortPolicyWithReport(name, url));
    }
}

与FixedThreadPool很类似,但是不同的是,创建线程池时,corePoolSize和maximumPoolSize参数不同,这里就不多解释JAVA线程池的原理了。另外,空闲的线程不会回收,会一直存在,因为线程池的keepAliveTime是Long.MAX_VALUE,这一点和FixedThreadPool不同。

EagerThreadPool类:

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);

        // 初始化taskQueue
        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;
    }
}

与其他代码类似,但是不同的是,这里的等待队列,不是JDK的LindedBlokingQueue,而是一个TaskQueue,返回的executor不是JDK中的Executor而是EagerThreadPoolExecutor!!!我们首先看一下TaskQueue的代码

public class TaskQueue<R extends Runnable> extends LinkedBlockingQueue<Runnable> {
    private EagerThreadPoolExecutor executor;
    public void setExecutor(EagerThreadPoolExecutor exec) {
        executor = exec;
    }
    @Override
    public boolean offer(Runnable runnable) {
        if (executor == null) {
            throw new RejectedExecutionException("...");
        }
        int currentPoolThreadSize = executor.getPoolSize();
        if (executor.getSubmittedTaskCount() < currentPoolThreadSize) {
            // 入队
            return super.offer(runnable);
        }
        if (currentPoolThreadSize < executor.getMaximumPoolSize()) {
            return false;
        }
        return super.offer(runnable);
    }
    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);
    }
}

 

删掉一些不重要的代码可发现,TaskQueue继承了LinkedBlokingQueue,覆写了offer方法。并且在构造方法里初始化了EagerThreadPoolExecutor,我们分析一下EagerThreadPoolExecutor的源码


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) {
       // ...
        submittedTaskCount.incrementAndGet();
        try {
            super.execute(command);
        } catch (RejectedExecutionException rx) {
            // 如果线程池满导致执行拒绝策略后
            final TaskQueue queue = (TaskQueue) super.getQueue();
            try {
                // 将任务入队TaskQueue
                if (!queue.retryOffer(command, 0, TimeUnit.MILLISECONDS)) {

                    throw new RejectedExecutionException("Queue capacity is full.", rx);
                }
            } catch (InterruptedException x) {
               // ...
            }
        } catch (Throwable t) {
            // ...
        }
    }
}

这段代码就很有意思,我们看核心代码execute方法,如果线程池满载执行拒绝策略后,会将任务再次投递到TaskQueue中。

CachedThreadPool类:


public class CachedThreadPool implements ThreadPool {
    @Override
    public Executor getExecutor(URL url) {
        // 获取线程名
        String name = url.getParameter(Constants.THREAD_NAME_KEY, Constants.DEFAULT_THREAD_NAME);
        // 获取corePoolSize
        int cores = url.getParameter(Constants.CORE_THREADS_KEY, Constants.DEFAULT_CORE_THREADS);
        // 获取maximumPoolSize
        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 NamedInternalThreadFactory(name, true), new AbortPolicyWithReport(name, url));
    }
}

 

与FixedThteadPool和LimitedThreadPool类型线程池不同的是,这里设置了线程池的keepAliveTime参数,当线程空闲时间达到后,会回收线程。

 

线程池的确定时机

那么,Dubbo是在什么时候确定使用什么类型的线程池呢?我们以AllDispatcher线程模型为例(AllDispatcher线程模型参考:Apache Dubbo系列:Dubbo线程模型)


public class AllDispatcher implements Dispatcher {
    public static final String NAME = "all";
    @Override
    public ChannelHandler dispatch(ChannelHandler handler, URL url) {
        return new AllChannelHandler(handler, url);
    }
}
public class AllChannelHandler extends WrappedChannelHandler {
    public AllChannelHandler(ChannelHandler handler, URL url) {
        super(handler, url);
    }
    // ...
}

 

AllDispatcher的disparch返回的AllChannelHandler的构造方法中,调用父类即WrappedChannelHandler的构造方法,我们继续跟进


public WrappedChannelHandler(ChannelHandler handler, URL url) {
    this.handler = handler;
    this.url = url;
    // Dubbo SPI机制,加载ThreadPool
    executor = (ExecutorService) ExtensionLoader.getExtensionLoader(ThreadPool.class).getAdaptiveExtension().getExecutor(url);
    // ...
}

这里,还是通过Dubbo SPI机制,加载ThreadPool这个扩展点。所以大家,Dubbo的SPI机制真的是贯穿于整个Dubbo框架,请大家务必熟悉Dubbo SPI(推荐阅读:Apache Dubbo系列:增强SPI)

 

如何扩展线程池

那么我们在Dubbo原有的线程池的基础上,我们怎么扩展自己的线程池呢?

很简单,我们要实现ThreadPool接口并覆写getExecutor方法,伪代码如下:


class MyThreadPool implements ThreadPool{
    @Override
    public Executor getExecutor(URL url) {
        return new ThreadPoolExecutor(1,1,0, TimeUnit.SECONDS,new LinkedBlockingQueue<>());
    }
}

还要在/META-INF/dubbo目录下创建org.apache.dubbo.common.threadpool.ThreadPool文件,文件内容是mythreadpool=com.xxx.MyThreadPool,并修改相关配置文件即可。

 

好了,Dubbo的线程池策略,今天就为大家介绍到这里,如果有不正确的地方或有疑问,请留言指出,谢谢大家。

 

推荐阅读:

Apache Dubbo系列:Dubbo线程模型

Apache Dubbo系列:集群容错整体架构

Apache Dubbo系列:Netty与Dubbo是如何对接的

Apache Dubbo系列:增强SPI

Apache Dubbo系列:ZooKeeper注册中心

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值