关于 Tomcat 的线程池的理解
默认配置下,Tomcat 会为每个连接器创建一个绑定的线程池(最大线程数 200)。在大多数情况下你不需要改这个配置(除非增大最大线程数以满足高负载需要)。但是 Tomcat 喜欢在每个工作者线程的 thread-local 上下文缓存一些诸如 PageContext 以及标签缓存的对象。正因如此,就会有你期望 Tomcat 能够将线程关掉以清理出来一些内存的情况。此外,每个连接器维护自己的线程池的话,根据服务器的承受能力来设置一个(线程数)最高值会变得更加困难。解决这些问题的答案就是使用一个共享执行器。
通过让所有的连接器都使用同一个共享的执行器,你可以预先对的整个应用能够承载的最高并发请求数进行相关配置。执行器也让线程池具备了闲时收缩忙时扩展的功能。至少在理论上是这样的...
org.apache.catalina.core.StandardThreadExecutor
Tomcat 默认所使用的标准、内置执行器就是 StandardThreadExecutor。配置文档访问:http://tomcat.apache.org/tomcat-6.0-doc/config/executor.html。这些配置选项里有个取名不当的参数 "maxIdleTime",以下是关于标准执行器和空闲线程的关闭你需要了解的一些事情。
标准执行器内在地使用了一个 java.util.concurrent.ThreadPoolExecutor。它通过具有一个变量大小的工作线程的线程池进行工作,一旦这些线程完成了一个任务,将会等待一个阻塞队列,直到一个新的任务进来。或者直到它等待了一个设定的时间,这时将会 "超时",该线程将被关闭。这里边的关键点是第一个完成了一个任务的线程会首先被分配新的任务,线程池遵守一个先进先出(FIFO)的模式。在我们检查它将如何影响 Tomcat 执行器的时候我们需要时刻注意这一点。
maxIdleTime 实际上是 minIdleTime
由于 Java ThreadPoolExecutor 的 FIFO 行为,每个线程在可能被关闭之前会等待最少 "maxIdleTime" 时间来接受新的任务。此外,还是由于线程池的 FIFO 的行为,因为最先进入空闲状态的线程会被优先分配新任务,所以在它被关闭之前它最少也要等待 maxIdleTime 没有任何请求进入的时间。这个的影响是执行器实际上无法为线程池设置适合平均负载(并发请求)的大小,它会更加请求进入的速度来调配这个大小。这看起来好像没啥差别,但是对于 web 服务器来讲,影响可就大了。举个例子,同一时间进来了 40 个请求。线程池将扩展到 40 以适应该负载。之后的一段期间,同一时间只进入了一个请求。比方说每个请求的执行结束需要 500 ms,这就意味着在接下来的这段时间内,需要 20 秒才能把整个线程池里的线程执行一遍(记住,FIFO)。除非你把你的 maxIdleTime 设置到 20 秒以内,否则线程池将会一直持有 40 个线程,即使你的并发量从未超过 1。然而你也并不想把你的 maxIdleTime 设置的太小 - 这将导致你面临线程被关闭的太快的风险。
结论
为了匹配平均负载,而不是一个请求进入的比率的话,要得到一个可以预期的线程池行为,比较合适的是执行器应该基于一个后进先出(LIFO)的模式。如果线程池能够将最小等待空闲的线程来优先分配进入的任务的话,服务器就能够在较低负载阶段更好地关闭线程(以一个更加可以预测的方式)。在上面那个再简单不过的例子中,初始负载为 40 之后一段时间的负载维持在 1,一个 LIFO 的线程池就能够在 maxIdleTime 阶段之后将大小合理地调整到 1。当然,并非总是要求你使用这种策略,但是如果你的目标是把 Tomcat 所持有的资源最小化,很不幸的是标准的执行器可能就不是你所期望的那样了。
原文链接:https://papweb.wordpress.com/2010/10/30/understanding-tomcat-executor-thread-pooling/。