【tomcat】Tomcat如何扩展Java线程池原理

池化技术

在后端中,对于经常使用池化就是来提升系统性能,比如数据库连接池、线程池连接池等,本质都是利用空间换时间的来提升性能,用来避免资源的频繁创建和销毁,以此提高资源的复用率,所以合理设置系统所需的线程池大小非常重要,一般都需要结合线程启动监控系统来观察,查看设置的是否合理。但是也有缺点,那就是如何无脑的设置,可能会占用过多的内存。所以要避免出现空间过度使用出现内存泄露和频繁垃圾回收的问题。

Java线程池核心原理

不清楚线程池工作原理的,可以看如下文章,从使用到源码解析。

【源码解析】聊聊线程池 实现原理与源码深度解析(一)

【源码解析】聊聊线程池 实现原理与源码深度解析(二)

【Java并发】聊聊线程池原理以及实际应用

【Java并发】聊聊创建线程池的几种方式以及实际生产如何应用

Tomcat自定义线程池

java 复制代码
    // 自定义的线程队列 最大值是 Integer.MAX_VALUE;
   taskqueue = new TaskQueue(maxQueueSize);
   // 自定义线程工厂,名称是tomcat-exec- (所以这就是为什么日志中是打印的它)
   TaskThreadFactory tf = new TaskThreadFactory(namePrefix,daemon,getThreadPriority());
   // 自定义线程池
   // 最小线程数 20 最大线程数 200  空闲时间 60S
   executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), maxIdleTime, TimeUnit.MILLISECONDS,taskqueue, tf);
    

可以看到 上面在初始化线程池的时候,创建了一个自定义线程队列以及一个线程工厂。

tomcat自定义线程处理流程

这里对着执行流程进行梳理下,

1.前corePoolSize个任务时,就创建核心线程处理

2.再来任务,就放入任务队列中让所有线程去抢,队列满了,创建临时线程执行。

3.达到最大线程maxNumPoolSize, 继续尝试把任务添加到任务队列中。

4.缓冲队列也满了,插入失败,执行拒绝策略。

我们看具体的code实现,可以发现,先调用java原生线程池执行,超过maximumPoolSize,java原生线程池抛出拒绝策略。尝试放入任务队列中,如果失败,抛出异常。

java 复制代码
    public void execute(Runnable command, long timeout, TimeUnit unit) {
        // 执行一个任务加1
        submittedCount.incrementAndGet();
        try {
            // 调用java原生线程池的execute去执行任务
            super.execute(command);
        } catch (RejectedExecutionException rx) {
            // 如果线程数达到最大 maximumPoolSize  Java原生线程池执行拒绝策略
            if (super.getQueue() instanceof TaskQueue) {
                final TaskQueue queue = (TaskQueue)super.getQueue();
                try {
                    // //继续尝试把任务放到任务队列中去
                    if (!queue.force(command, timeout, unit)) {
                        // 执行失败 -1
                        submittedCount.decrementAndGet();
                        // //如果缓冲队列也满了,插入失败,执行拒绝策略。
                        throw new RejectedExecutionException(sm.getString("threadPoolExecutor.queueFull"));
                    }
                } catch (InterruptedException x) {
                    //  // 执行失败 -1
                    submittedCount.decrementAndGet();
                    throw new RejectedExecutionException(x);
                }
            } else {
                //  // 执行失败 -1
                submittedCount.decrementAndGet();
                throw rx;
            }

        }
    }

tomcat自定义任务队列

从如下代码中可以看到,自定义了一个任务队列,因为这个队列是一个无界队列,达到核心线程后,就无法创建线程,直接将任务阻塞到队列中。所以通过 submittedCount.incrementAndGet(); submittedCount.decrementAndGet(); 记录当前已经提交到线程池,但是还没有执行完的任务个数。

在任务队列的长度无限制的情况下,让线程池有机会创建新的线程。
当然默认情况下Tomcat的任务队列是没有限制的,你可以通过设置maxQueueSize参数来限制任务队列的长度。

java 复制代码
public class TaskQueue extends LinkedBlockingQueue<Runnable> {
    // 构造方法 调用父类构造方法
    public TaskQueue(int capacity) {
        super(capacity);
    }

	  @Override
    // 线程池调用任务队列的方法时,当前线程数肯定已经大于核心线程数了
    public boolean offer(Runnable o) {
      //we can't do any checks
        if (parent==null) return super.offer(o);
        //we are maxed out on threads, simply queue the object
        // //如果线程数已经到了最大值,不能创建新线程了,只能把任务添加到任务队列。
        if (parent.getPoolSize() == parent.getMaximumPoolSize()) return super.offer(o);
        //we have idle threads, just add it to the queue
        //执行到这里,表明当前线程数大于核心线程数,并且小于最大线程数。
        //表明是可以创建新线程的,那到底要不要创建呢?分两种情况:

        //1. 如果已提交的任务数小于当前线程数,表示还有空闲线程,无需创建新线程
        if (parent.getSubmittedCount()<=(parent.getPoolSize())) return super.offer(o);
        //if we have less threads than maximum force creation of a new thread

        //2. 如果已提交的任务数大于当前线程数,线程不够用了,返回false去创建新线程
        if (parent.getPoolSize()<parent.getMaximumPoolSize()) return false;
        //if we reached here, we need to add it to the queue

        //默认情况下总是把任务添加到任务队列
        return super.offer(o);
    }
}

小总结

虽然面试中,对于线程池的问题很多,但是如果我们可以结合tomcat自定义线程池的原理来进行复补充,那么不仅可以体现我们对框架内部理解的深度,也可以提升对八股文的应用能力。

tomcat是如何设计的,其实主要就是继承原生ThreadPoolExecutor,重写execute(), 定制自己的任务处理流程。

相关推荐
安之若素^8 分钟前
启用不安全的HTTP方法
java·开发语言
ruanjiananquan9915 分钟前
c,c++语言的栈内存、堆内存及任意读写内存
java·c语言·c++
一个天蝎座 白勺 程序猿25 分钟前
Python(28)Python循环语句指南:从语法糖到CPython字节码的底层探秘
开发语言·python
chuanauc42 分钟前
Kubernets K8s 学习
java·学习·kubernetes
持梦远方1 小时前
C 语言基础入门:基本数据类型与运算符详解
c语言·开发语言·c++
一头生产的驴1 小时前
java整合itext pdf实现自定义PDF文件格式导出
java·spring boot·pdf·itextpdf
YuTaoShao1 小时前
【LeetCode 热题 100】73. 矩阵置零——(解法二)空间复杂度 O(1)
java·算法·leetcode·矩阵
zzywxc7871 小时前
AI 正在深度重构软件开发的底层逻辑和全生命周期,从技术演进、流程重构和未来趋势三个维度进行系统性分析
java·大数据·开发语言·人工智能·spring
灵性花火1 小时前
Qt的前端和后端过于耦合(0/7)
开发语言·前端·qt
DES 仿真实践家2 小时前
【Day 11-N22】Python类(3)——Python的继承性、多继承、方法重写
开发语言·笔记·python