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

时间: 2024-07-30 10:17:45

Tomcat线程池,更符合大家想象的可扩展线程池的相关文章

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

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

线程池? 如何设计一个动态大小的线程池,有哪些方法?

[线程池?  如何设计一个动态大小的线程池,有哪些方法?] 线程池:顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中, 需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中, 从而减少创建和销毁线程对象的开销. 系统启动一个新线程的成本是比较高的,因为它涉及与操作系统的交互.此时,使用线程池可以很好地提高性能,尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池. 与数据库连接池相似,线程池在系统启动时即创建大量空闲的线程,程序将一个Runnable

java5线程池详解与Executors类创建不同线程池的用法

java中的线程池是非常重要的,它可以节省资源开销,从而提升程序的性能.向Tomcat等一些web服务器都必须用到线程池.java5中为我们提供了一些应用线程池的API,下面的代码将详解其用法. package hxl.insist; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorSer

Notepad++ 快捷键修改使之更符合Emacs的用户习惯

Notepad++ 快捷键修改使之更符合Emacs的用户习惯,主要是设置时会与NOTEPAD++自带的键冲突,例如ALT+F,这个优先级是高于用户自定义的快捷键,最后是通过录制宏的方式解决.使用只需要将下面的XML替换用户目录下的shortcuts.xml(C:\Users\xxx\AppData\Roaming\Notepad++)便可以了. <NotepadPlus> <InternalCommands> <Shortcut id="41001" Ct

43_2013年11月22日 线程池 Socket(Thread Lock Process 摇奖 线程池ThreadPool)

1>模拟线程池,生产者消费者问题 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Product { class Program { static void Main(string[] args) { //创建一个池子 MyConncetion[]

SEO之HTML优化:让你的网站HTML代码更符合SEO规范

摘要HTML优化是网站内部优化的重点,可能对SEO新手来说,容易忽略.符合搜索引擎习惯的HTML代码是极利于SEO的,可以让你的网站获得更好的搜索引擎排名.如何制作一个标准的HTML网页,如何做HTML优化,本文将为你详细阐述. 一直想写下SEO中关于HTML优化方面的的文章,总算空出这部分时间来了.其实,HTML优化应该是网站内部优化的重点,每个SEOer都需高度重视.根据本人多年实战经验,一般做过HTML优化的网站上线后,相关关键词都可以获得一个不错的搜索引擎排名.显然,符合搜索引擎习惯的H

第11章 Windows线程池(1)_传统的Windows线程池

第11章 Windows线程池 11.1 传统的Windows线程池及API 11.1.1 传统的线程池对象及对应的API 线程池对象 API 普通任务线程池 QueueUserWorkItem 计时器线程池 CreateTimerQueue(创建线程池) CreateTimerQueueTimer(创建计时器) ChangeTimerQueueTimer DeleteTimerQueueTimer DeteTimerQueueEx 同步对象等待线程池 RegisterWaitForSingle

Android 的线程(AsyncTask、HandlerThread、IntentService详解)和线程池

Android 的线程和线程池 在操作系统中,线程是操作系统调度的最小单元,同时线程又是一种受限的系统资源,即线程不可能无限制的产生,并且线程的创建和销毁都有一定的开销. 当系统中存在大量的线程时,系统会通过时间片轮转的方式调度每个线程,因此线程不可能做到绝对的并发,除非线程数小于等于CUP的核心数,一般来说这是不可能的.如果在一个进程中频繁的创建和销毁线程,这显然不是高效的做法.正确的做法是采用线程池,一个线程池中会缓存一定数量的线程,通过线程池就可以避免因为频繁创建和销毁线程所带来的系统开销

java并发:线程池、饱和策略、定制、扩展

一.序言 当我们需要使用线程的时候,我们可以随时新建一个线程,这样实现起来非常简便,但在某些场景下存在缺陷:如果需要同时执行多个任务(即并发的线程数量很多),频繁地创建线程会降低系统的效率,因为创建和销毁线程均需要一定的时间.线程池可以使线程得到复用,所谓线程复用就是线程在执行完一个任务后并不被销毁,该线程可以继续执行其他的任务. 二.Executors提供的线程池 Executors是线程的工厂类,也可以说是一个线程池工具类,Executors提供的线程都是通过参数设置来实现不同的线程池机制.