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

前言

Tomcat 线程池,是依据 JUC 中的线程池 ThreadPoolExecutor 重新自定义实现的。
其执行线程的代码逻辑,和JUC 中是相同的。主要区别在于,Tomcat中对 阻塞队列进行了改造。

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

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

正文

一、执行线程的基本流程

Tomcat 中执行线程的基本流程,和JUC中是一致的。
以下贴出两种执行方法。

1.1 JUC中的线程池执行线程

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

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 中线程池执行线程

@Override
public void execute(Runnable command) {
   
    execute(command,0,TimeUnit.MILLISECONDS);
}

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

@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

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(…)

@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(…)

其本质是,直接入队列。

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

相关推荐

  1. Tomcat线原理(下篇工作原理)

    2024-02-22 10:00:04       30 阅读
  2. 线 核心原理

    2024-02-22 10:00:04       17 阅读
  3. 线原理和基本使用~

    2024-02-22 10:00:04       42 阅读
  4. 线的运行原理和使用案例

    2024-02-22 10:00:04       41 阅读
  5. 深入Elasticsearch:线原理与应用

    2024-02-22 10:00:04       33 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-02-22 10:00:04       19 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-02-22 10:00:04       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-02-22 10:00:04       19 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-02-22 10:00:04       20 阅读

热门阅读

  1. springcloud多网卡注册nacos时IP控制方法

    2024-02-22 10:00:04       24 阅读
  2. 人工智能与开源机器学习框架

    2024-02-22 10:00:04       31 阅读
  3. [OpenGL教程05 ] glAccum() 函数对累积缓存设置

    2024-02-22 10:00:04       24 阅读
  4. spring缓存的使用

    2024-02-22 10:00:04       28 阅读
  5. 保存Json对象到数据库

    2024-02-22 10:00:04       31 阅读
  6. LeetCode--代码详解 4.寻找两个正序数组的中位数

    2024-02-22 10:00:04       26 阅读
  7. Docker实战之下载Mysql、Redis、Zimg

    2024-02-22 10:00:04       38 阅读