Tomcat线程池原理(下篇:工作原理)

文章目录

前言

Tomcat 线程池,是依据 JUC 中的线程池 ThreadPoolExecutor 重新自定义实现的。

其执行线程的代码逻辑,和JUC 中是相同的。主要区别在于,Tomcat中对 阻塞队列进行了改造。

本文主要研究 Tomcat 的线程池是如何执行线程的,即线程池的工作原理。

同系列文章:Tomcat线程池原理(上篇:初始化原理)

正文

一、执行线程的基本流程

Tomcat 中执行线程的基本流程,和JUC中是一致的。

以下贴出两种执行方法。

1.1 JUC中的线程池执行线程

ThreadPoolExecutor中,执行线程的方法定义如下:

java 复制代码
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    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);
    }
    else if (!addWorker(command, false))
        reject(command);
}

1.2 Tomcat 中线程池执行线程

java 复制代码
@Override
public void execute(Runnable command) {
    execute(command,0,TimeUnit.MILLISECONDS);
}

重载方法定义如下:

这个方法被Deprecated标注,源码中注释中描述说是,Tomcat10中会被删除,估计是一种优化。本文不做研究。

java 复制代码
@Deprecated
public void execute(Runnable command, long timeout, TimeUnit unit) {
	// 提交的任务数量+1
    submittedCount.incrementAndGet();
    try {
    	// 执行线程任务(这个方法中的代码和JUC中执行线程的代码一致,差别在于,阻塞队列使用了Tomcat自定义的队列)
        executeInternal(command);
    } catch (RejectedExecutionException rx) {
    	// 被拒绝后,尝试将线程放入队列
    	// 如果当前队列是Tomcat自定义的队列TaskQueue,尝试将任务放入队列
        if (getQueue() instanceof TaskQueue) {
            final TaskQueue queue = (TaskQueue) 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 {
        	// 当前指定的队列不是TaskQueue,提交的任务数-1,并抛出异常
            submittedCount.decrementAndGet();
            throw rx;
        }
    }
}

另外,executeInternal 的内容如下(和JUC中基本一致):区别在于阻塞队列换成了TaskQueue

java 复制代码
private void executeInternal(Runnable command) {
    if (command == null) {
        throw new NullPointerException();
    }
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true)) {
            return;
        }
        c = ctl.get();
    }
    // 这里的workQueue,实际已经换成了TaskQueue
    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);
        }
    }
    else if (!addWorker(command, false)) {
        reject(command);
    }
}

二、被改造的阻塞队列

从第一小节中,看到的东西并不多。因为,Tomcat线程池的改造重心,在于阻塞队列,也就是 TaskQueue

2.1 TaskQueue的 offer(...)

java 复制代码
@Override
public boolean offer(Runnable o) {
  //we can't do any checks
    if (parent==null) {
        return super.offer(o);
    }
    // 若是达到最大线程数,直接进队列
    if (parent.getPoolSizeNoLock() == parent.getMaximumPoolSize()) {
        return super.offer(o);
    }
    // 已提交任务数小于等于当前线程数
    // 对应的场景是:当前活跃线程为10个,但是只有8个任务在执行,于是,直接进队列
    if (parent.getSubmittedCount() <= parent.getPoolSizeNoLock()) {
        return super.offer(o);
    }
    // 当前线程数小于最大线程数,此次拒绝进入队列
    if (parent.getPoolSizeNoLock() < parent.getMaximumPoolSize()) {
        return false;
    }
    //if we reached here, we need to add it to the queue
    return super.offer(o);
}

2.2 TaskQueue的 force(...)

其本质是,直接入队列。

java 复制代码
public boolean force(Runnable o) {
    if (parent == null || parent.isShutdown()) {
        throw new RejectedExecutionException(sm.getString("taskQueue.notRunning"));
    }
    return super.offer(o); //forces the item onto the queue, to be used if the task is rejected
}

三、总结

根据源码中对于TaskQueue的改造,可以观察出,Tomcat线程池的主要特点如下:

  • 当前线程数小于corePoolSize,则去创建工作线程;
  • 当前线程数大于corePoolSize,但小于maximumPoolSize,则去创建工作线程;
  • 当前线程数大于maximumPoolSize,则将任务放入到阻塞队列中,当阻塞队列满了之后,则调用拒绝策略丢弃任务;
  • 任务执行失败时不会直接抛出错误,而是装回队列里再次尝试执行;
  • 当线程池没有达到最大执行线程的时候,会优先开线程再使用任务队列;

总结之后就是,Tomcat 为了更适配 IO 密集型任务,改造了阻塞队列。与JUC相比,会先去创建线程执行任务,创建的线程数达到最大线程数时,再放入队列等待空闲线程的出现。

相关推荐
勤匠22 分钟前
spring shell 基础使用
java·linux·spring
Code季风1 小时前
测试驱动开发(TDD)实战:在 Spring 框架实现中践行 “红 - 绿 - 重构“ 循环
java·驱动开发·后端·spring·设计模式·springboot·tdd
hello早上好1 小时前
JPA、缓存、数据源与连接池、简介
java·mybatis
Bella_chene1 小时前
IDEA中使用Servlet,tomcat输出中文乱码
servlet·tomcat·intellij-idea
想要成为祖国的花朵1 小时前
Java_Springboot技术框架讲解部分(二)
java·开发语言·spring boot·spring
vvilkim2 小时前
深入理解设计模式:原型模式(Prototype Pattern)
java·设计模式·原型模式
通域2 小时前
Mac (m1) Java 加载本地C共享库函数 .dylib 函数 Unable to load library ‘liblicense‘
java·python·macos
hqxstudying2 小时前
Java行为型模式---模板方法模式
java·开发语言·设计模式·代码规范·模板方法模式
iam_leeqing3 小时前
Lambda表达式
java·spring
你我约定有三3 小时前
设计模式--适配器模式
java·设计模式·适配器模式