Tomcat NIO分析

tomcat nio并不是真正的异步io,其实是io复用,可以说是非阻塞的,但不是真正的异步。
tomcat的NioEndpoint启动的ServerSocket是阻塞的,Acceptor线程里边阻塞从accept()获取socket
socket是非阻塞的,每个socket的channel注册到一个Poller线程上去,Poller会把这个channel包装成一个PollerEvent,并放到events队列里去。

默认有两个Poller线程,每个Poller线程里边有一个Selector,以及一个队列:SynchronizedQueue<PollerEvent> events
btw:Acceptor线程和Poller线程都是NioEndpoint的内部类实现的)

Poller线程内部不断循环events队列 1、从里边拿出PollerEvent去执行它的run方法,其中将PollerEvent的SocketChannel注册到该poller的Selector中。
2、SocketChannel如果readable则执行processKey()
processKey()里边调用processSocket(),processSocket()里边把socket包装成SocketProcessor交给线程池去执行。

线程池
在NioEndpoint的startInternal()方法里,也就是在启动Poller和Acceptor线程之前把线程池建立起来createExecutor()
//创建线程池
public void createExecutor() {
internalExecutor = true;
TaskQueue taskqueue = new TaskQueue(); //任务队列
TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());//工程模式,告诉线程池怎么创建里边的线程
//参数:核心线程数、最大线程数、60秒keepalive、任务队列、以及用来创建线程用的线程工厂
executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
taskqueue.setParent( (ThreadPoolExecutor) executor);//任务队列里边存了一下线程池的引用,以后会用到
}

接下来就是研究TaskQueue、ThreadPoolExecutor了

看过来其实tomcat线程池还是基于jdk线程池的,只是在初始化的时候传入了自定义的TaskQueue和ThreadFactory, 从而达到自定义线程池的目的。

最终目的是要分析好两份jstact样本:
异常,将近400个处于WAITING
"http-nio-8081-exec-385" #459 daemon prio=5 os_prio=0 tid=0x00007f4b6819a800 nid=0x674e waiting on condition [0x00007f4b2e977000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000006c8ddb088> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2083)
at java.util.concurrent.LinkedBlockingQueue.poll(LinkedBlockingQueue.java:467)
at org.apache.tomcat.util.threads.TaskQueue.poll(TaskQueue.java:85)
at org.apache.tomcat.util.threads.TaskQueue.poll(TaskQueue.java:31)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1073)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
正常样本: 无请求时,tomcat线程池中idle线程10个,等待处理请求
"http-nio-8081-exec-1" #17 daemon prio=5 os_prio=0 tid=0x00007f3bf8aa9800 nid=0x15d0 waiting on condition [0x00007f3bd54d4000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000e41b3ad8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:103)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)

to be continuted ...

 

 

上面的问题在于tomcat线程池的maxIdle配的太大了,比如coreSize=minIdle=10, maxIdle=400, maxActive=600,那么一旦高峰时候线程数来到了600,高峰退去,线程释放到maxIdle就不会释放了,maxIdle意思是最大运行空闲常驻多少线程。

这里边还有个疑问,tomcat9的代码里没看到有关“maxIdle”的参数,就coreSize、maxSize、maxkeepalivetime这几个参数,coresize是常驻,超过coresize小于maxsize的,maxkeepalivetime之内没有使用则线程释放,然后等的这段时间应该是poll(timeout)、对应的应该是TIMED_WAIT状态。

这里是WAITING状态,跟coresize时候常驻线程发现taskqueue里边为空然后一直阻塞等待非空的情况一样都是WAITING,然后org.apache.tomcat.util.threads.TaskQueue.poll(TaskQueue.java:85)这句又指向线程是在做timeout(keepAliveTimeout默认60s)超时等待。

概况来说,笔者感觉从线程栈上来看是从任务队列里拿任务时,线程阻塞了poll是超时阻塞,多线程并发从队列里poll。(这里未必是队列里为空,所以如果可以的话要对这个队列加个监控、看一下队列里任务数),是不是线程都是在从队列里拿任务的时候都阻塞在锁上了?由于某种性能上的原因,等待锁的过程又很慢?

妈的,如果队列里拿任务然后拿完就释放锁,这个过程应该很快,其他线程非公平的争抢锁,然后抢到以后就poll,暂时想不出队列里有任务大家还都卡在poll上的情形。。。

 

tomcat的worker线程的空闲判定与释放 - 简书 (jianshu.com)

Tomcat NIO线程模型与IO方式分析 - 简书 (jianshu.com)

posted on 2020-02-03 18:54  肥兔子爱豆畜子  阅读(2284)  评论(0编辑  收藏  举报

导航