Tomcat线程池,更符合大家想象的可扩展线程池
因由
说起线程池,大家可能受连接池的印象影响,天然的认为,它应该是一开始有core条线程,忙不过来了就扩展到max条线程,闲的时候又回落到core条线程,如果还有更高的高峰,就放进一个缓冲队列里缓冲一下。
有些整天只和SSH打交道的同学,可能现在还是这样认为的。
无情的现实就是,JDK只有两种典型的线程池,FixedPool 与 CachedPool:
- FixedPool固定线程数,忙不过来的全放到无限长的缓冲队列里。
- CachedPool,忙不过来时无限的增加临时线程,闲时回落,没有缓冲队列。
在Java ThreadPool的正确打开方式里,建议了如何设置,避免上面两句吓人的“无限”。但无论怎么配,都无法用现成的东西,配出文章一开始的想象图来。
但不得不说,这幅想象图还是比较美好的,特别对于偶有卡顿,偶有不可预测高峰的业务线程池来说。
当然,也有人说请求积压在最后的缓冲队列里不好控制,看具体业务场景了,缓冲队列也有不同的玩法(后详)。
原理
我们的同事老王,研究了一番ThreadPool的机制后,提出了自己实现队列的方式。碰巧,Tomcat也正是这么做的,比起Jetty完全自己写线程池,Tomcat基于JDK的线程池稍作定制,要斯文一些。
JDK线程池的逻辑很简单( 更详细描述还是见Java ThreadPool的正确打开方式 )
- 前core个请求,来一个请求就创建一个线程。
- 之后,把请求插入缓冲队列里让所有的线程去抢;如果插入失败则创建新线程。
- 如果达到max条线程了,抛出拒绝异常。
貌似控制的枢纽都在第2句那里--队列插入的结果。JDK也是通过使用LinkedBlockingQueue 与 特殊的SynchronousQueue,实现自己的控制效果。
那我可不可以自己封装一个Queue,在插入时增加以下逻辑呢?
- 如果当前有空闲线程等待接客,则把任务加入队列让孩儿们去抢。
- 如果没有空闲的了,总线程数又没到达max,那就返回false,让Executor去创建线程。
- 如果总线程数已达到max,则继续把任务加入队列缓冲一下。
- 如果缓冲队列也满了,抛出拒绝异常。
说白了就是这么简单。
Tomcat的实现
Tomcat的TaskQueue实现:
public class TaskQueue extends LinkedBlockingQueue {
@Override
public boolean offer(Runnable o) {
if (parent . getPoolSize() == parent . getMaximumPoolSize()) return super . offer(o);
if (parent . getSubmittedCount() < parent.getPoolSize()) return super . offer(o);
if (parent . getPoolSize() < parent . getMaximumPoolSize()) return false;
return super.offer(o);
}
}
非常简单的代码,唯一要说明的是,如何判断当前有没有空闲的线程等待接客。JDK的CachedPool是靠特殊的零长度的SynchronousQueue实现。而Tomcat则靠扩展Executor ,增加一个当前请求数的计数器,在execute()方法前加1,再重载afterExecute()方法减1,然后判断当前线程总数是否大于当前请求总数就知道有咩有围观群众。
更进一步
因为相信Tomcat这种百年老店,我们就不自己写这个池了,把Tomcat实现里一些无关需求剥掉即用。
但Tomcat就完美了吗?
首先,TaskQueue的offer()里,调用了executor.getPoolSize(),这是个有锁的函数,这是最遗憾的地方,在大家都在嫌线程池里一条队列锁得太厉害,像ForkJoinPool或Netty的设计都是一个线程一个队列时,这个有锁的函数相当碍眼。而且,最过分的是,Tomcat居然一口气调了三次(在Tomcat9 M9依然如此)。反复看了下,不求那么精准的话貌似一次就够了,真的有并发的变化的情况,executor里还有个处理RejectException,把任务重新放回队列的保险。
最后,说说缓冲队列的两种玩法:
一种是队列相对比较长,比如4096,主线程把任务丢进去就立刻返回了,如果队列满了就直接报拒绝异常。
一种是队列相对比较短的,比如512,如果满了,主线程就以queue.force(command, timeout)等在那里等队列有空,等到超时才报拒绝异常。
Tomcat的机制支持这两种玩法,自己设置就好。
文章可能还要修改,转载请保留原文链接,否则视为侵权:
http://calvin1978.blogcn.com/articles/tomcat-threadpool.html
Tomcat线程池,更符合大家想象的可扩展线程池的更多相关文章
- 原生线程池这么强大,Tomcat 为何还需扩展线程池?
前言 Tomcat/Jetty 是目前比较流行的 Web 容器,两者接受请求之后都会转交给线程池处理,这样可以有效提高处理的能力与并发度.JDK 提高完整线程池实现,但是 Tomcat/Jetty 都 ...
- Java:多线程,线程池,用Executors静态工厂生成常用线程池
一: newSingleThreadExecutor 创建一个单线程的线程池,以无界队列方式运行.这个线程池只有一个线程在工作(如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它.)此线程池 ...
- Java线程状态、线程start方法源码、多线程、Java线程池、如何停止一个线程
下面将依次介绍: 1. 线程状态.Java线程状态和线程池状态 2. start方法源码 3. 什么是线程池? 4. 线程池的工作原理和使用线程池的好处 5. ThreadPoolExecutor中的 ...
- 详解线程池的作用及Java中如何使用线程池
服务端应用程序(如数据库和 Web 服务器)需要处理来自客户端的高并发.耗时较短的请求任务,所以频繁的创建处理这些请求的所需要的线程就是一个非常消耗资源的操作.常规的方法是针对一个新的请求创建一个新线 ...
- 零基础学习java------day18------properties集合,多线程(线程和进程,多线程的实现,线程中的方法,线程的声明周期,线程安全问题,wait/notify.notifyAll,死锁,线程池),
1.Properties集合 1.1 概述: Properties类表示了一个持久的属性集.Properties可保存在流中或从流中加载.属性列表中每个键及其对应值都是一个字符串 一个属性列表可包含另 ...
- java并发:线程池、饱和策略、定制、扩展
一.序言 当我们需要使用线程的时候,我们可以新建一个线程,然后显式调用线程的start()方法,这样实现起来非常简便,但在某些场景下存在缺陷:如果需要同时执行多个任务(即并发的线程数量很多),频繁地创 ...
- SEO之HTML优化:让你的网站HTML代码更符合SEO规范
摘要HTML优化是网站内部优化的重点,可能对SEO新手来说,容易忽略.符合搜索引擎习惯的HTML代码是极利于SEO的,可以让你的网站获得更好的搜索引擎排名.如何制作一个标准的HTML网页,如何做HTM ...
- 第11章 Windows线程池(1)_传统的Windows线程池
第11章 Windows线程池 11.1 传统的Windows线程池及API (1)线程池中的几种底层线程 ①可变数量的长任务线程:WT_EXECUTELONGFUNCTION ②Timer线程:调用 ...
- 更符合面向对象的数据库操作方式-OrmLite
1. jar包下载 下载地址:http://ormlite.com/releases/,一般用core和android包即可. 如果使用的是android studio,也可以直接通过module s ...
随机推荐
- PostgreSql中如何kill掉正在执行的sql语句
虽然可以使用 kill -9 来强制删除用户进程,但是不建议这么去做. 因为:对于执行update的语句来说,kill掉进程,可能会导致Postgres进入到recovery mode 而在recov ...
- jboolean
bool为C中变量类型,jboolean 为JNI中变量类型,boolean为Java中变量类型:jboolean在C语言的定义为:typedef unsigned char jboolean;uns ...
- class_create()
#define class_create(owner, name) \({ \ ...
- Java 包装类 自动装箱和拆箱
包装类(Wrapper Class) 包装类是针对于原生数据类型的包装. 因为有8个原生数据类型,所以对应有8个包装类. 所有的包装类(8个)都位于java.lang下. Java中的8个包装类分别是 ...
- 事务报错 [Exception] 当前 TransactionScope 已完成
捕获异常的时候 偶尔会碰到这个异常报告 导致原因 //正确代码 using (TransactionScope ts = new TransactionScope(TransactionScopeOp ...
- Android异常之 unable to write jarlist cache file
异常: android开发调试时候不能运行,出现 unable to write jarlist cache file 错误. 解决方法: 1.找到appcompt文件夹如下的位置.
- uva 11624 Fire!(搜索)
开始刷题啦= = 痛并快乐着,学到新东西的感觉其实比看那些无脑的小说.电视剧有意思多了 bfs裸体,关键是先把所有的着火点放入队列,分开一个一个做bfs会超时的 发现vis[][]是多余的,完全可以用 ...
- 省常中模拟 Test2 Day2
two 模拟 大意:给你一个 N 位二进制数,有四种操作:加1.减1.乘2.整除2.给定一个操作序列,求最终结果.N <= 5*10^6.数据保证不会在最高位上进行进位或退位操作. 初步解法:由 ...
- C++类的构造、拷贝构造、析构函数等
1: 一个空的class在C++编译器处理过后就不再为空,编译器会自动地为我们声明一些member function,如果你写 class A{}; 编译器处理后,就相当于: class A{ pub ...
- hdu 2204 Eddy's爱好
// 一个整数N,1<=N<=1000000000000000000(10^18).// 输出在在1到N之间形式如M^K的数的总数// 容斥原理// 枚举k=集合{2,3,5,7,11,1 ...