前言
最近在学习过程中遇到在某个场景下:修改某条数据时,给该线程上分布式写锁,然后引入延迟队列处理其他请求;这个方案有一定的缺点,因为在用到消息队列时,不存在占用过多线程从而导致OOM的问题,消费者组只会安排固定的几个线程去拉取消息,如果碰到上面那种拿不到锁的情况,阻塞等待就好。
上面这个场景设计到了Tomcat 的线程池,因此做一些分享。
Tomcat 的线程池在工作方式上有一些不同于普通的 Java 线程池 (如 ThreadPoolExecutor
) 的地方,尤其是在处理线程的创建和任务的排队方面。
普通线程池(ThreadPoolExecutor)的工作方式
普通的 Java 线程池 (ThreadPoolExecutor) 按以下顺序处理任务:
- 核心线程 :首先会创建和使用核心线程来处理任务,直到核心线程数达到
corePoolSize
。 - 阻塞队列:如果核心线程都在忙,并且有新的任务进来,任务会被放入阻塞队列(workQueue)中。
- 最大线程数 :如果阻塞队列满了,并且还有新的任务,线程池会创建新的线程,直到线程总数达到
maximumPoolSize
。 - 拒绝策略 :如果线程总数已经达到了
maximumPoolSize
,并且阻塞队列也满了,新来的任务会根据拒绝策略(RejectedExecutionHandler)来处理。
Tomcat 线程池的工作方式
Tomcat 使用了一个自定义的线程池实现,它在处理请求时的行为与普通线程池有一些不同:
- 核心线程与最大线程 :Tomcat 线程池中的
maxThreads
参数对应于普通线程池中的maximumPoolSize
,而minSpareThreads
参数则与corePoolSize
类似,但有一些不同。 - 直接创建新线程 :Tomcat 在线程创建方面更加激进,当有新的请求进来时,如果所有当前线程都在忙,它会立即尝试创建新的线程,直到达到
maxThreads
数量。 - 任务队列 :Tomcat 也有一个任务队列(
acceptCount
),但它的作用是当所有maxThreads
都在忙时,用于排队等待处理的请求数量。如果这个队列也满了,新的请求会被拒绝或连接被关闭。 - 线程回收 :Tomcat 会回收空闲的线程,保持至少
minSpareThreads
数量的线程处于空闲状态以便应对新的请求。
具体的区别
-
线程创建时机:
- 普通线程池:先使用核心线程,然后任务进入队列,再创建额外线程。
- Tomcat 线程池:在核心线程忙时,会直接创建新的线程直到
maxThreads
。
-
队列的使用:
- 普通线程池:核心线程忙时,任务排队,队列满时才创建新的线程。
- Tomcat 线程池:在所有线程都忙时,任务才进入队列。
-
参数配置:
- 普通线程池:
corePoolSize
,maximumPoolSize
,keepAliveTime
,workQueue
。 - Tomcat 线程池:
maxThreads
,minSpareThreads
,acceptCount
等。
- 普通线程池:
示例
普通线程池(ThreadPoolExecutor)示例
java
ExecutorService executorService = new ThreadPoolExecutor(
10, // corePoolSize
20, // maximumPoolSize
60L, // keepAliveTime
TimeUnit.SECONDS, // unit
new LinkedBlockingQueue<Runnable>(100) // workQueue
);
Tomcat 线程池配置示例(在server.xml
中)
xml
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
maxThreads="200"
minSpareThreads="25"
acceptCount="100"
maxConnections="5000"/>
在这个配置中:
- maxThreads :最大线程数,表示 Tomcat 能够创建的最大工作线程数(类似于
maximumPoolSize
)。 - minSpareThreads:最小空闲线程数,即使没有请求,Tomcat 也会保持的空闲线程数。
- acceptCount:最大排队请求数,当所有线程都在忙时,新的请求会进入这个队列。
总结
- 普通线程池:主要通过核心线程处理任务,队列排队,最后才扩展到最大线程数。
- Tomcat 线程池 :更注重快速响应,会在核心线程忙时立即创建新线程,直到达到
maxThreads
,只有在所有线程都忙时才使用队列来排队请求。
这种差异使得 Tomcat 在处理高并发 Web 请求时更加高效,因为它能够迅速扩展线程池以应对突然增加的请求负载,同时保持合理的空闲线程数来应对新请求。