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相比,会先去创建线程执行任务,创建的线程数达到最大线程数时,再放入队列等待空闲线程的出现。

相关推荐
消失的旧时光-19431 小时前
Spring Boot 工程化进阶:统一返回 + 全局异常 + AOP 通用工具包
java·spring boot·后端·aop·自定义注解
NE_STOP1 小时前
Redis--发布订阅命令和Redis事务
java
PAC_3Dame1 小时前
记一次真实的线上OOM
java
SunnyDays10111 小时前
如何在Java中将Word文档转换为图像(JPEG、PNG或SVG)
java
Lumos_7772 小时前
Linux -- 线程
java·jvm·算法
知兀2 小时前
【MybatisPlus】后端用枚举类,数据库用tinyint,存在枚举类型转换
java
StockTV2 小时前
印度股票实时数据 NSE和BSE的实时行情、K 线及指数数据
java·开发语言·spring boot·python
User_芊芊君子2 小时前
【OpenAI 把 AI 玩明白了】:自主推理 + 动态知识图谱,这 4 个技术突破要颠覆行业
java·人工智能·知识图谱
c++之路2 小时前
C++20概述
java·开发语言·c++20
Championship.23.243 小时前
Linux Top 命令族深度解析与实战指南
java·linux·服务器·top·linux调试