JAVA面试题分享五百二十八:tomcat线程池配置和请求流程

目录

配置和说明

默认配置创建的线程池

请求进入tomcat处理流程


版本: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,源码

AbstractEndpointprivate 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();    }

线程池最终执行请求。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

之乎者也·

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值