目录
版本:tomcat9
配置和说明
org.apache.tomcat.util.threads.ThreadPoolExecutor
是对jdk线程池的重写。
tomcat9默认Connector
配置如下:
默认注释掉了Executor配置,默认下,Connector 会根据自己的配置,创建一个线程池
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
以上隐含很多默认配置,线程相关主要配置
maxConnections
最大连接数量,默认8*1024(windows)下,配置成-1,则不限制连接数量。
tomcat的请求限流配置,采用的是AQS锁机制,一个请求进来时,会执行+1操作,如果此刻请求数量大于配置maxConnections的值,则阻塞等待,直到socket请求处理完毕,会执行-1操作,其他业务整个处理完后,当前值小于配置值,才放行请求。
所有请求执行前tomcat内部限流,此时并未到socket阻塞阶段和线程池阶段。
默认值源码:
类AbstractEndpoint
private int maxConnections = 8*1024;
public void setMaxConnections(int maxCon) {
this.maxConnections = maxCon;
LimitLatch latch = this.connectionLimitLatch;
if (latch != null) {
// Update the latch that enforces this
if (maxCon == -1) {
releaseConnectionLatch();
} else {
latch.setLimit(maxCon);
}
} else if (maxCon > 0) {
initializeConnectionLatch();
}
}
//底层是 LimitLatch对象实现的AQS锁
判断源码:
类 Acceptor:
public void run() {
//省略其他代码
endpoint.countUpOrAwaitConnection();//这里实现+1,并判断是否阻塞
//如果此刻请求数<=maxConnections, 得到socket请求,最终交给线程池处理
socket = endpoint.serverSocketAccept();
}
类 AbstractEndpoint
protected void countUpOrAwaitConnection() throws InterruptedException {
if (maxConnections==-1) {
return;
}
LimitLatch latch = connectionLimitLatch;
if (latch!=null) {
latch.countUpOrAwait(); //执行+1 ,判断是否阻塞
}
}
minSpareThreads
最小线程数,默认10,源码
AbstractEndpoint
private int minSpareThreads = 10;
请求数量大于当前线程池,则根据线程池,往队列里排
maxThreads
最大线程数,默认200,源码
AbstractEndpoint下
private int maxThreads = 200;
如果队列排满,则扩充线程,这里需要注意,默认配置下队列是无界的,也就是Integer.MAX_VALUE
但是,以上已经说明,tomcat有内部限流,所以他只会小于maxConnections,并不存在oom风险
acceptCount
socket的队列,线程池处理,如果线程池满了,则socket塞入当前队列(非线程池队列),如果当前也满了,如果是请求端,则抛出以下错误
java.lang.RuntimeException:
java.net.ConnectException: Connection refused: connect
不通过代理直接请求网页显示拒绝连接请求
代理上就是503,这是socket拒绝的,也可以说是tomcat拒绝的
connectionTimeout
客户端与服务器socket连接超时时间,单位ms,默认:20000,源码:
类 AbstractEndpoint内
public int getConnectionTimeout() {
return socketProperties.getSoTimeout();
}
SocketProperties:
protected Integer soTimeout = Integer.valueOf(20000);
要注意,超时并不是一个请求发送过去,20秒后没处理完超时,而是socket握手时,客户端和服务端先建立连接,再发送数据,如果建立连接不发送数据,有个超时时间,就是此配置。以下是测试:
public static void main(String[] args) throws Exception {
Socket socket = new Socket("127.0.0.1", 8080);
long start = System.currentTimeMillis();
InputStream is = socket.getInputStream();
is.read();
System.out.println(System.currentTimeMillis() - start);
}
如果超时设置2s,则打印2s左右
默认配置创建的线程池
tomcat 默认配置下创建的线程池
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);
}
最小线程,默认20,最大线程,默认200,超时60秒,队列采用TaskQueue,TaskQueue 继承,无界队重写了LinkedBlockingQueue,上文有强调,一般不会出现因队列导致的oom。
请求进入tomcat处理流程
-
tomcat 启动加载时,
NioEndpoint
创建两个线程+一个线程池
createExecutor
创建默认线程池,tomcat核心线程池
startAcceptorThread();
启用线程,加载Acceptor
实现,主要作用是监听请求socket,并包装一下
启用一个线程加载Poller
,实现对以上包装的处理
-
Acceptor
内run方法 采用while 循环,会在endpoint.serverSocketAccept()
内阻塞
protected SocketChannel serverSocketAccept() throws Exception {
SocketChannel result = serverSock.accept();//阻塞
return result;
}
当一个请求到来时,阻塞被唤醒放行,返回一个socket对象,代表一个请求
-
socket会包装为
socketWrapper
,注册成poller事件,并加到poller内
public void register(final NioSocketWrapper socketWrapper) {
socketWrapper.interestOps(SelectionKey.OP_READ);
PollerEvent pollerEvent = createPollerEvent(socketWrapper, OP_REGISTER);
addEvent(pollerEvent);
}
-
同一时刻,
Poller
线程循环监听是否有socket对象到来
private long selectorTimeout = 1000;
keyCount = selector.select(selectorTimeout);//阻塞1s超时继续执行
线程会阻塞1s,当有socket到来时,被唤醒执行,代码:
private void addEvent(PollerEvent event) {
events.offer(event);
if (wakeupCounter.incrementAndGet() == 0) {
selector.wakeup();
}
}
-
poller处理事件后,交给线程池处理
public boolean processSocket(SocketWrapperBase<S> socketWrapper,
SocketEvent event, boolean dispatch) {
Executor executor = getExecutor();
if (dispatch && executor != null) {
executor.execute(sc);
} else {
sc.run();
}
线程池最终执行请求。