前言
在Java Web的開發(fā)過程中,Tomcat常用的web容器。SpringBoot之前,我們用的是單獨的 Tomcat,SpringBoot時代,嵌入了Tomcat。
在Jdk中,JUC內(nèi)有線程框架,以及可以自定義參數(shù)配置的 TreadPoolExecutor。Tomcat內(nèi)也實現(xiàn)了自己的線程池。
所謂線程池,是被用來處理傳入的 HTTP 請求的。
當客戶端發(fā)送請求時,Tomcat 會從線程池中獲取一個可用的線程來處理該請求。處理完請求后,線程將返回線程池,并在下一個請求到來時再次被重用。
究其原因,是JUC內(nèi)的線程池不符合Tomcat的使用場景。
- Jdk中的線程池,是cpu密集型(也就是偏計算,處理完了可以去隊列再取任務)
- Tomcat的應用場景,卻大多是IO密集型的。(也就是要求IO盡量不要阻塞,任務先處理,實在處理不了了,再進阻塞隊列)
下圖是JUC中線程池處理任務的流程:
與JUC中明顯不同的一點是,Tomcat為了處理IO,減少阻塞的情況,
本系列文章就是專門探討Tomcat中線程池的原理,分為上下兩篇,本文是上篇,主要介紹Tomcat中線程池的初始化原理。
本系列文章基于SpringBoot2.7.6,其內(nèi)嵌的tomcat版本是9.0.69。
同系列文章:Tomcat線程池原理(下篇:工作原理)
正文
本系列文章核心內(nèi)容是Tomcat的線程池原理,因此在畫圖,文字描述時會忽略部分不涉及的內(nèi)容。
一、從啟動腳本開始分析
使用過Tomcat的同學都知道,我們單獨的啟動tomcat時,是從腳本入手的。
啟動tomcat , 需要調(diào)用 bin/startup.bat (在linux 目錄下 , 需要調(diào)用 bin/startup.sh)
在startup.bat 腳本中, 調(diào)用了catalina.bat。
在catalina.bat 腳本文件中,調(diào)用了BootStrap 中的main方法。
后續(xù)的操作如下圖:
簡而言之,就是逐級的 init()
和 start()
。
而本文的關注點,就是 ProtocolHandler
的 start()
,也就是圖中的最后一步。
二、ProtocolHandler 的啟動原理
關鍵在于 EndPoint
的 start()
。
而在Tomcat 中,會執(zhí)行到 AbstractEndPoint
的 start()
。具體代碼如下:
public final void start() throws Exception {
if (bindState == BindState.UNBOUND) {
bindWithCleanup();
bindState = BindState.BOUND_ON_START;
}
startInternal();
}
public abstract void startInternal() throws Exception;
也就是說真正的啟動方法是AbstractEndPoint
子類實現(xiàn)的startInternal()
。
三、AbstractEndPoint 的啟動原理
在Tomcat中,有3個AbstractEndPoint
的子類。
在8.5/9.0版本中,使用的是其中的 NioEndPoint
類。
本文就使用默認的 NioEndPoint
進行分析。
接第二小節(jié), NioEndPoint
在執(zhí)行startInternal()
時,會判斷是否存在線程池,如果沒有,會創(chuàng)建默認的線程池。對應代碼如下:
@Override
public void startInternal() throws Exception {
if (!running) {
running = true;
paused = false;
if (socketProperties.getProcessorCache() != 0) {
processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getProcessorCache());
}
if (socketProperties.getEventCache() != 0) {
eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getEventCache());
}
if (socketProperties.getBufferPool() != 0) {
nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getBufferPool());
}
// 如果沒自定義線程池,則創(chuàng)建默認工作線程池
if (getExecutor() == null) {
createExecutor();
}
initializeConnectionLatch();
// Start poller thread
poller = new Poller();
Thread pollerThread = new Thread(poller, getName() + "-Poller");
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();
startAcceptorThread();
}
}
四、創(chuàng)建默認線程池
根據(jù)第三小節(jié)的分析,在沒自定義線程池,或者配置線程池時,會自動創(chuàng)建一個線程池。代碼如下:
public void createExecutor() {
internalExecutor = true;
TaskQueue taskqueue = new TaskQueue();
TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());
executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
taskqueue.setParent( (ThreadPoolExecutor) executor);
}
注意,ThreadPoolExecutor 不是JUC中的線程池了,其是Tomcat自己實現(xiàn)的線程池。
五、參數(shù)配置原理
日常工作中,總會遇到需要自己制定Tomcat線程池參數(shù)的情況。這一小節(jié)就來說明一下。
在Tomcat中,TomcatWebServerFactoryCustomizer
負責配置自定義參數(shù)。
在自動配置類 EmbeddedWebServerFactoryCustomizerAutoConfiguration
中配置了如下內(nèi)容:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class })
public static class TomcatWebServerFactoryCustomizerConfiguration {
@Bean
public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment,
ServerProperties serverProperties) {
return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
}
}
5.1 常規(guī)的參數(shù)配置
普通的參數(shù)配置可以參考ServerProperties
中的內(nèi)容。
# Tomcat連接數(shù)相關參數(shù)
# 最大連接數(shù),默認8192,一般要大于(tomcat.threads.max + tomcat.accept-count)
server.tomcat.max-connections=300
# 當所有工作線程都被占用時,新的連接將會放入等待隊列中的最大容量,默認100
server.tomcat.accept-count=50
# Tomcat線程池相關參數(shù)
# 最大線程池大小,默認200
server.tomcat.threads.max=200
# 最小工作空閑線程數(shù)(核心線程數(shù)),默認10
server.tomcat.threads.min-spare=12
5.2 自定義線程池
如果普通的參數(shù)配置,不能滿足你的需求,則需要自定義線程池。
定義自己的類,繼承 TomcatWebServerFactoryCustomizer
,然后重寫customize
即可。
核心思路是,在AbstractProtocol
中設置線程池。
以下是我的示例:
package org.feng.demos.web;
import org.apache.coyote.AbstractProtocol;
import org.apache.coyote.ProtocolHandler;
import org.apache.tomcat.util.threads.TaskQueue;
import org.apache.tomcat.util.threads.TaskThreadFactory;
import org.apache.tomcat.util.threads.ThreadPoolExecutor;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.embedded.TomcatWebServerFactoryCustomizer;
import org.springframework.boot.web.embedded.tomcat.ConfigurableTomcatWebServerFactory;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* 自定義tomcat線程池
*
* @author feng
*/
@Component
public class MyTomcatWebServerFactoryCustomizer extends TomcatWebServerFactoryCustomizer {
public MyTomcatWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
super(environment, serverProperties);
}
@Override
public void customize(ConfigurableTomcatWebServerFactory factory) {
super.customize(factory);
// 自定義tomcat線程池
System.out.println("自定義tomcat線程池--start");
// 自定義tomcat線程池
factory.addConnectorCustomizers((connector) -> {
ProtocolHandler handler = connector.getProtocolHandler();
if (handler instanceof AbstractProtocol) {
AbstractProtocol protocol = (AbstractProtocol) handler;
TaskQueue taskqueue = new TaskQueue();
TaskThreadFactory tf = new TaskThreadFactory("feng" + "-exec-", true, 5);
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 60L, TimeUnit.SECONDS, taskqueue, tf);
protocol.setExecutor(threadPoolExecutor);
taskqueue.setParent(threadPoolExecutor);
}
});
System.out.println("自定義tomcat線程池--end");
}
}
5.3 測試自定義線程
定義如下方法:文章來源:http://www.zghlxwxcb.cn/news/detail-835635.html
// http://127.0.0.1:8080/hello?name=lisi
@RequestMapping("/hello")
@ResponseBody
public String hello(@RequestParam(name = "name", defaultValue = "unknown user") String name) {
System.out.println("當前線程名:" + Thread.currentThread().getName());
return "Hello " + name;
}
調(diào)用時,控制臺打?。?br>文章來源地址http://www.zghlxwxcb.cn/news/detail-835635.html
到了這里,關于Tomcat線程池原理(上篇:初始化原理)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!