线程和进程都是操作系统的基本概念,一个运行中的应用程序可以被看做是一个进程,而线程,是运行中的实际的任务执行者。一个进程可以包含多个同时运行的线程。

前文《Java面试必考问题:线程的生命周期 》介绍了Java线程的基本概念。Tomcat作为一个web容器,是以一个进程的形式运行的;当一个请求到达后,Tomcat就会创建一个线程来处理,请求处理完成后再把线程销毁掉。

这意味着在一个程序运行过程中,需要多次创建并销毁线程,创建并销毁线程的过程势必会消耗内存。为了节省内存资源的消耗,就提出了线程池的概念。

JDK提供了创建线程池的类:Executor,而实际中通常使用它的子类:ThreadPoolExecutor.

spring 线程池 工具类 spring线程池和tomcat线程池_spring 线程池 工具类

Java面试必考问题:Tomcat为啥不用Java原生线程池

线程池ThreadPoolExecutor构造函数

ThreadPoolExecutor 线程池有很多重要参数:

spring 线程池 工具类 spring线程池和tomcat线程池_spring 线程池 工具类_02

Java面试必考问题:Tomcat为啥不用Java原生线程池

线程池keepAliveTime参数只对非核心线程有效

当有任务进来时,线程池的处理流程如下:

spring 线程池 工具类 spring线程池和tomcat线程池_线程池_03

Java面试必考问题:Tomcat为啥不用Java原生线程池

线程池的任务处理流程

线程池满负荷运转后, 因为时间空间的问题, 可能需要拒绝掉部分任务的执行。JDK内置了几种拒绝策略:

我们也可以通过重载 RejectedExecutionHandler 类的 rejectedExecution() 函数,来自定义拒绝策略。

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

所以究竟是不是线程越多越好,也要看具体任务的类型。

Tomcat 也用到了线程池技术,Tomcat的线程池直接继承自 java.util.concurrent.ThreadPoolExecutor,并重新写了部分方法逻辑。那么Tomcat为什么不用JDK原生线程池呢?

从上面介绍的JDK原生线程池的任务处理逻辑可知,只要线程池线程数量大于核心线程数,就会将任务先加入到任务队列中,只有任务加入失败才会再新建线程。也就是说任务队列未满之前,最多只有核心线程在工作。

这种尽量少创建线程的策略,比较适合处理 CPU密集型任务,但是对于 IO 密集型任务,如数据库查询,RPC请求调用,就不是很友好了。

类似Tomcat、Jetty这种web容器,需要处理大量客户端请求任务,属于IO密集型任务,如果采用原生线程池,一旦接受请求数量大于线程池核心线程数,这些请求就会被放入到队列中等待。这样做显然会降低整体的请求处理速度,所以Tomcat扩展了JDK 原生线程池。