当前位置: 首页 > news >正文

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

文章目录

  • 前言
  • 正文
    • 一、执行线程的基本流程
      • 1.1 JUC中的线程池执行线程
      • 1.2 Tomcat 中线程池执行线程
    • 二、被改造的阻塞队列
      • 2.1 TaskQueue的 offer(...)
      • 2.2 TaskQueue的 force(...)
    • 三、总结

前言

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) {// 提交的任务数量+1submittedCount.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)) {// 放入队列失败,提交的任务数-1submittedCount.decrementAndGet();// 抛出异常,线程池队列已满throw new RejectedExecutionException(sm.getString("threadPoolExecutor.queueFull"));}} catch (InterruptedException x) {// 中断时,提交的任务数-1submittedCount.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,实际已经换成了TaskQueueif (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 checksif (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 queuereturn 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相比,会先去创建线程执行任务,创建的线程数达到最大线程数时,再放入队列等待空闲线程的出现。

相关文章:

  • JAVA工程师面试专题-Mysql篇
  • 浅谈redis之SDS
  • 宝物筛选(二进制优化多重背包)
  • 数据结构与算法:图形数据结构
  • 解决弹性布局父元素设置高自动换行,子元素均分高度问题(align-content: flex-start)
  • 【思路】短链生成及访问
  • vivo 基于 StarRocks 构建实时大数据分析平台,为业务搭建数据桥梁
  • 获取视频第一帧,以及后续上传
  • Zabbix 6.2.1 安装
  • Jenkins解决Host key verification failed (2)
  • RandAugment(NeurIPS 2020)论文速读
  • C++学习规划“的 PPT 大纲设计
  • sql注入 [极客大挑战 2019]FinalSQL1
  • hbuilderx创建、运行uni-app
  • B树的介绍
  • [iOS]Core Data浅析一 -- 启用Core Data
  • 【干货分享】SpringCloud微服务架构分布式组件如何共享session对象
  • 10个确保微服务与容器安全的最佳实践
  • Cumulo 的 ClojureScript 模块已经成型
  • Invalidate和postInvalidate的区别
  • Java知识点总结(JDBC-连接步骤及CRUD)
  • JSONP原理
  • Js基础知识(一) - 变量
  • js面向对象
  • MySQL Access denied for user 'root'@'localhost' 解决方法
  • node和express搭建代理服务器(源码)
  • Python 反序列化安全问题(二)
  • React中的“虫洞”——Context
  • Travix是如何部署应用程序到Kubernetes上的
  • vue总结
  • 对话:中国为什么有前途/ 写给中国的经济学
  • 关于for循环的简单归纳
  • 猫头鹰的深夜翻译:JDK9 NotNullOrElse方法
  • 盘点那些不知名却常用的 Git 操作
  • 前端学习笔记之原型——一张图说明`prototype`和`__proto__`的区别
  • 设计模式 开闭原则
  • 通过来模仿稀土掘金个人页面的布局来学习使用CoordinatorLayout
  • 写代码的正确姿势
  • 一道面试题引发的“血案”
  • 优秀架构师必须掌握的架构思维
  • [地铁译]使用SSD缓存应用数据——Moneta项目: 低成本优化的下一代EVCache ...
  • #HarmonyOS:Web组件的使用
  • #stm32整理(一)flash读写
  • #调用传感器数据_Flink使用函数之监控传感器温度上升提醒
  • $ git push -u origin master 推送到远程库出错
  • (20050108)又读《平凡的世界》
  • (C语言)深入理解指针2之野指针与传值与传址与assert断言
  • (规划)24届春招和25届暑假实习路线准备规划
  • (三) prometheus + grafana + alertmanager 配置Redis监控
  • (十二)springboot实战——SSE服务推送事件案例实现
  • (转)h264中avc和flv数据的解析
  • (转)scrum常见工具列表
  • (转)程序员疫苗:代码注入
  • (转)德国人的记事本
  • ***微信公众号支付+微信H5支付+微信扫码支付+小程序支付+APP微信支付解决方案总结...