dubbo之线程池

写在前面

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

如下是生成的转存文件和其部分内容:

在这里插入图片描述

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值