【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(), 定制自己的任务处理流程。

相关推荐
程序员的世界你不懂1 小时前
【Flask】测试平台开发,新增说明书编写和展示功能 第二十三篇
java·前端·数据库
星空寻流年1 小时前
设计模式第一章(建造者模式)
java·设计模式·建造者模式
lingchen19061 小时前
MATLAB的数值计算(三)曲线拟合与插值
开发语言·matlab
gb42152872 小时前
java中将租户ID包装为JSQLParser的StringValue表达式对象,JSQLParser指的是?
java·开发语言·python
一朵梨花压海棠go2 小时前
html+js实现表格本地筛选
开发语言·javascript·html·ecmascript
曾经的三心草2 小时前
Python2-工具安装使用-anaconda-jupyter-PyCharm-Matplotlib
android·java·服务器
蒋星熠2 小时前
Flutter跨平台工程实践与原理透视:从渲染引擎到高质产物
开发语言·python·算法·flutter·设计模式·性能优化·硬件工程
Metaphor6922 小时前
Java 高效处理 Word 文档:查找并替换文本的全面指南
java·经验分享·word
ChinaRainbowSea2 小时前
7. LangChain4j + 记忆缓存详细说明
java·数据库·redis·后端·缓存·langchain·ai编程
stormsha2 小时前
飞算JavaAI炫技赛电商系统商品管理模块的架构设计与实现
java·架构·鸿蒙系统