前言

Tomcat/Jetty 是目前比较流行的 Web 容器,两者接受请求之后都会转交给线程池处理,这样可以有效提高处理的能力与并发度。JDK 提高完整线程池实现,但是 Tomcat/Jetty 都没有直接使用。Jetty 采用自研方案,内部实现 QueuedThreadPool 线程池组件,而 Tomcat 采用扩展方案,踩在 JDK 线程池的肩膀上,扩展 JDK 原生线程池。

JDK 原生线程池可以说功能比较完善,使用也比较简单,那为何 Tomcat/Jetty 却不选择这个方案,反而自己去动手实现那?

JDK 线程池

通常我们可以将执行的任务分为两类:

  • cpu 密集型任务
  • io 密集型任务

cpu 密集型任务,需要线程长时间进行的复杂的运算,这种类型的任务需要少创建线程,过多的线程将会频繁引起上文切换,降低任务处理处理速度。

而 io 密集型任务,由于线程并不是一直在运行,可能大部分时间在等待 IO 读取/写入数据,增加线程数量可以提高并发度,尽可能多处理任务。

JDK 原生线程池工作流程如下:

详情可以查看 一文教你安全的关闭线程池,上图假设使用 LinkedBlockingQueue
灵魂拷问:上述流程是否记错过?在很长一段时间内,我都认为线程数量到达最大线程数,才放入队列中。 ̄□ ̄||

上图中可以发现只要线程池线程数量大于核心线程数,就会先将任务加入到任务队列中,只有任务队列加入失败,才会再新建线程。也就是说原生线程池队列未满之前,最多只有核心线程数量线程。

这种策略显然比较适合处理 cpu 密集型任务,但是对于 io 密集型任务,如数据库查询,rpc 请求调用等,就不是很友好了。

由于 Tomcat/Jetty 需要处理大量客户端请求任务,如果采用原生线程池,一旦接受请求数量大于线程池核心线程数,这些请求就会被放入到队列中,等待核心线程处理。这样做显然降低这些请求总体处理速度,所以两者都没采用 JDK 原生线程池。

解决上面的办法可以像 Jetty 自己实现线程池组件,这样就可以更加适配内部逻辑,不过开发难度比较大,另一种就像 Tomcat 一样,扩展原生 JDK 线程池,实现比较简单。

下面主要以 Tomcat 扩展线程池,讲讲其实现原理。

扩展线程池

首先我们从 JDK 线程池源码出发,查看如何这个基础上扩展。

可以看到线程池流程主要分为三步,第二步根据 queue#offer 方法返回结果,判断是否需要新建线程。

JDK 原生队列类型 LinkedBlockingQueue, SynchronousQueue,两者实现逻辑不尽相同。

LinkedBlockingQueue

offer 方法内部将会根据队列是否已满作为判断条件。若队列已满,返回 false,若队列未满,则将任务加入队列中,且返回 true

SynchronousQueue

这个队列比较特殊,内部不会储存任何数据。若有线程将任务放入其中将会被阻塞,直到其他线程将任务取出。反之,若无其他线程将任务放入其中,该队列取任务的方法也将会被阻塞,直到其他线程将任务放入。

对于 offer 方法来说,若有其他线程正在被取方法阻塞,该方法将会返回 true。反之,offer 方法将会返回 false。

所以若想实现适合 io 密集型任务线程池,即优先新建线程处理任务,关键在于 queue#offer 方法。可以重写该方法内部逻辑,只要当前线程池数量小于最大线程数,该方法返回 false,线程池新建线程处理。

当然上述实现逻辑比较糙,下面我们就从 Tomcat 源码查看其实现逻辑。

Tomcat 扩展线程池

Tomcat 扩展线程池直接继承 JDK 线程池 java.util.concurrent.ThreadPoolExecutor,重写部分方法的逻辑。另外还实现了 TaskQueue,直接继承 LinkedBlockingQueue,重写 offer 方法。

首先查看 Tomcat 线程池的使用方法。

可以看到 Tomcat 线程池使用方法与普通的线程池差不太多。

接着我们查看一下 Tomcat 线程池核心方法 execute 的逻辑。

execute 方法逻辑比较简单,任务核心还是交给 Java 原生线程池处理。这里主要增加一个重试策略,如果原生线程池执行拒绝策略的情况,抛出 RejectedExecutionException 异常。这里将会捕获,然后重新再次尝试将任务加入到 TaskQueue ,尽最大可能执行任务。

这里需要注意 submittedCount 变量。这是 Tomcat 线程池内部一个重要的参数,它是一个 AtomicInteger 变量,将会实时统计已经提交到线程池中,但还没有执行结束的任务。也就是说 submittedCount 等于线程池队列中的任务数加上线程池工作线程正在执行的任务。 TaskQueue#offer 将会使用该参数实现相应的逻辑。

接着我们主要查看 TaskQueue#offer 方法逻辑。

核心逻辑在于第三步,这里如果 submittedCount 小于当前线程池线程数量,将会返回 false。上面我们讲到 offer 方法返回 false,线程池将会直接创建新线程。

Dubbo 2.6.X 版本增加 EagerThreadPool,其实现原理与 Tomcat 线程池差不多,感兴趣的小伙伴可以自行翻阅。

折衷方法

上述扩展方法虽然看起不是很难,但是自己实现代价可能就比较大。若不想扩展线程池运行 io 密集型任务,可以采用下面这种折衷方法。

new ThreadPoolExecutor(10, 10,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(100));

不过使用这种方式将会使 keepAliveTime 失效,线程一旦被创建,将会一直存在,比较浪费系统资源。

总结

JDK 实现线程池功能比较完善,但是比较适合运行 CPU 密集型任务,不适合 IO 密集型的任务。对于 IO 密集型任务可以间接通过设置线程池参数方式做到。

tomcat 查看当前请求数_原生线程池这么强大,Tomcat 为何还需扩展线程池?相关推荐

  1. 原生线程池这么强大,Tomcat 为何还需扩展线程池?

    前言 Tomcat/Jetty 是目前比较流行的 Web 容器,两者接受请求之后都会转交给线程池处理,这样可以有效提高处理的能力与并发度.JDK 提高完整线程池实现,但是 Tomcat/Jetty 都 ...

  2. 查看 并发请求数及其TCP连接状态

    服务器上的一些统计数据: 1)统计80端口连接数 netstat -nat|grep -i "80"|wc -l 2)统计httpd协议连接数 ps -ef|grep httpd| ...

  3. pycharm 查看代码行数_【收藏】提高PyCharm效率的10个小技巧

    PyCharm是最常用的python开发IDE,程序员可以通过PyCharm强大的功能节约大量时间用来 摸鱼 工作,提高生产效率. 阿狗总结了10个自己会用到的PyCharm中可以提高撸码效率的小技巧 ...

  4. 查看php-fpm进程数_查看php-fpm开启的进程数以及每个进程的内存限制

    查看php-fpm开启的进程数以及每个进程的内存限制 1.通过命令查看服务器上一共开了多少的 php-cgi 进程 ps -fe |grep "php-fpm"|grep &quo ...

  5. phpstrom查看代码总行数_歪特内推浦发银行总行信息科技部

    WHITer内推--每天9点发布武汉优质互联网企业最新岗位内推机会.内推微信号:whxiaowai  内推邮箱:770554595@qq.com 浦发银行总行信息科技部武汉开发中心 简历筛选流程较慢, ...

  6. tomcat catalina localhost 没有项目_实用shell脚本--一键配置tomcat定期日志清理功能

    概述 日志文件包含了关于系统中发生的事件的有用信息,在排障过程中或者系统性能分析时经常被用到.对于忙碌的服务器,日志文件大小会增长极快,服务器会很快消耗磁盘空间,这成了个问题.除此之外,处理一个单个的 ...

  7. java 内核线程_Java:如何根据cpu内核扩展线程?

    选项1: 来自的newWorkStealingPoolExecutors public static ExecutorService newWorkStealingPool() 使用所有可用处理器作为 ...

  8. springboot tomcat默认线程数_记一次JAVA线程池的错误用法

    最近项目一个项目要结项了,但客户要求 TPS 能达到上千,而用我写的代码再怎么弄成只能达到 30 + 的 TPS,然后我又将代码中能缓存的都缓存了,能拆分的也都拆分了,拆分时用的线程池来实现的:其实现 ...

  9. java压测请求线程数_程序员撕开京东 618 大促压测的另一面 | 原力计划

    作者 | 天涯泪小武 责编 | 王晓曼 出品 | CSDN博客 前天618大促演练进行了全链路压测,在此之前刚好我的热key探测框架也已经上线灰度一周了,小范围上线了几千台服务器,每秒大概接收几千个k ...

最新文章

  1. Centos 7 学习之静态IP设置
  2. 图片镂空算法集合[图](转)
  3. 我的世界java版怎么装在u盘_我的世界选择器参数怎么使用?
  4. TypeError: cannot perform reduce with flexible type
  5. Lolipa魔方财务主题-虚拟主机源码
  6. 《Android游戏开发详解》一2.2 设置开发机器
  7. [研究笔记]Lambda表达式学习笔记
  8. Unit04 - 继承的意义(下) 、 访问控制 、 static和final
  9. python求无序列表中位数_详解Python如何获取列表(List)的中位数
  10. BG.Hive - part3
  11. Webdriver常用的元素定位
  12. 数据结构 详解(C++)
  13. pip更新pip,升级 pip3怎么做?
  14. 在软件测试中UT,IT,ST,UAT分别是什么意思
  15. 西门子、三菱、台达PLC手机组态软件,支持modbus协议的ModbusTesla手机组态软件 只支持modbus tcp,只要下位机支持标准的modbus协议就可以
  16. Python中的 SciPy 样条曲线插值
  17. 现场直击 | 复旦MBA科创青干营开营
  18. startx 启动过程
  19. Chapter 76 - 89
  20. 用电脑无线投屏到电视屏幕的连接方法

热门文章

  1. 【Flink】Flink 的输出 Output CountingOutput
  2. 【Elasticsearch】 es 排查问题 explain 使用 内容解释
  3. Elasticsearch】es 模糊查询导致Elasticsearch服务宕机
  4. 【Es】ElasticSearch 自定义分词器
  5. 【Kafka】Elasticsearch 与 Kafka 整合剖析
  6. 60-150-040-使用-Sink-Flink自定义UpsertStreamTableSink
  7. 【Flink】Flink检查点时间太小导致Exceeded checkpoint tolerable failure threshould
  8. Spring : Importxxx系列注解
  9. 【安全】Apache HDFS 上配置 kerberos
  10. Rabbitmq的三种方式