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

相关推荐
祈祷苍天赐我java之术21 分钟前
Redis 有序集合解析
java·前端·windows·redis·缓存·bootstrap·html
Mr.Ja1 小时前
【LeetCode热题100】No.1——两数之和(Java)
java·算法·leetcode
编啊编程啊程2 小时前
gRPC从0到1系列【20】
java·rpc·kafka·dubbo·nio
万邦科技Lafite3 小时前
如何对接API接口?需要用到哪些软件工具?
java·前端·python·api·开放api·电商开放平台
1710orange4 小时前
java设计模式:静态代理模式
java·设计模式·代理模式
我真的是大笨蛋4 小时前
开闭原则详解(OCP)
java·设计模式·性能优化·开闭原则·设计规范
编啊编程啊程4 小时前
gRPC从0到1系列【19】
java·spring boot·rpc·dubbo·nio
泥嚎泥嚎4 小时前
【Android】Android 的三种动画(帧动画、View 动画、属性动画)
java
不良人天码星5 小时前
使用Java连接redis以及开放redis端口的问题
java·开发语言·redis
马克学长5 小时前
SSM村务管理系统s2qnw(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
java·服务器·数据库