NIOEndpoint由五部分组成,分别是LimitLatch,Acceptor,Poller,SocketProcessor,Executor。其实现了IO多路复用,也就是建立连接和读取字节流 是在不同的线程中执行,通过selector的死循环一直去监听网卡数据到来的事件,一旦由数据到达,就通过selector读取出来。
- LimitLatch
LimitLatch的目的是限制tomcat的连接数,默认值为10000,当连接数到达10000时,后面再过来的请求就需要等待前面的连接数释放了才能被tomcat处理。这里需要注意一点,这只是在应用层限制了连接数,但是操作系统底层还是会建立tcp的连接的。
LimitLatch是AbstractEndpoint抽象类持有。该类有一个名为Sync的内部类,Sync继承了AbstractQueuedSynchronizer。AbstractQueuedSynchronizer是 Java 并发包中的一个核心类,它在内部维护一个状态和一个线程队列,可以用来控制线程什么时候挂起,什么时候唤醒。JDK中的锁的实现就是基于AbstractQueuedSynchronizer来实现的。
下面是LimitLatch的核心代码:public class LimitLatch { private final Sync sync; private final AtomicLong count; private volatile long limit; private volatile boolean released = false; private class Sync extends AbstractQueuedSynchronizer { @Override protected int tryAcquireShared(int ignored) { long newCount = count.incrementAndGet(); if (!released && newCount > limit) { // Limit exceeded count.decrementAndGet(); return -1; } else { return 1; } } @Override protected boolean tryReleaseShared(int arg) { count.decrementAndGet(); return true; } }
tryAcquireShared是在请求到达是会调用该方法,当返回值为1时,该请求能继续进行下去;若返回值为-1时,请求就会把放在一个任务队列中。当有线程调用tryReleaseShared才会唤醒队列中的任务重新请求tryAcquireShared获取许可。
在LimitLatch中持有一个AtomicLong类型的 count字段。AtomicLong是一个基于CAS无锁的方式来记录连接数。
- Acceptor
Acceptor实现了Runnable接口,因此使得它能跑在一个单独的线程上。Acceptor是用来建立连接,并把到达的连接传送到Poller上,由Poller去读取网络发送的数据。
下面是Acceptor的核心代码(删减了不重要的代码):protected class Acceptor extends AbstractEndpoint.Acceptor { @Override public void run() { int errorDelay = 0; // Loop until we receive a shutdown command while (running) { if (!running) { break; } try { //if we have reached max connections, wait countUpOrAwaitConnection(); SocketChannel socket = null; socket = serverSock.accept(); // Successful accept, reset the error delay errorDelay = 0; // Configure the socket if (running && !paused) { // setSocketOptions() will hand the socket off to // an appropriate processor if successful if (!setSocketOptions(socket)) { closeSocket(socket); } } else { closeSocket(socket); } } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log.error(sm.getString("endpoint.accept.fail"), t); } } state = AcceptorState.ENDED; }
通过代码第7行可以看到,Acceptor会跑在一个死循环内。代码的14行最终就会调用到LimitLatch的tryAcquireShared方法上。然后在代码的第17行通过调用socket的accept方法来接受新连接。然后在26行的setSocketOptions方法内把SocketChannel封装成PollerEvent,并添加到Poller对象内的SynchronizedQueue属性中。SynchronizedQueue是一个加锁的队列,加锁的目的是为了在解决在高并发场景的数据不一致,而队列的目的就是为了让越早到达的请求越快被处理。setSocketOptions的调用链:setSocketOptions()->getPoller0().register()->addEvent()。至此,Acceptor就已经把请求连接交给Poller来处理了。
- Poller
Poller也实现了Runnable接口,它也是有个线程跑在死循环上,用来监听所有已经通过Acceptor建立的socket事件,它的作用就像网络编程模型中的Selector,当网卡有数据到达时,会生成SocketProcessor交由线程池Excecutor处理,SocketProcessor也是实现了Runnable接口的类。Poller主要流程的方法调用链:run()->processKey()->processSocket()。processSocket方法的作用是把SocketProcessor交由线程池Excecutor处理。下面是processSocket的源码:
public boolean processSocket(SocketWrapperBase<S> socketWrapper,
SocketEvent event, boolean dispatch) {
try {
if (socketWrapper == null) {
return false;
}
SocketProcessorBase<S> sc = processorCache.pop();
if (sc == null) {
sc = createSocketProcessor(socketWrapper, event);
} else {
sc.reset(socketWrapper, event);
}
Executor executor = getExecutor();
if (dispatch && executor != null) {
executor.execute(sc);
} else {
sc.run();
}
} catch (RejectedExecutionException ree) {
getLog().warn(sm.getString("endpoint.executor.fail", socketWrapper) , ree);
return false;
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
// This means we got an OOM or similar creating a thread, or that
// the pool and its queue are full
getLog().error(sm.getString("endpoint.process.fail"), t);
return false;
}
return true;
}
从代码的第5行可看到,SocketProcessorBase先从processorCache从取出,如果没有则新建一个;如果能获取到,则通过调用reset方法重制该对象。processorCache是SynchronizedStack类型的,之所以使用栈,应该是为了取值更快。然后在代码的14行就把刚才获取到的SocketProcessor交由Executor线程池处理了。
-
SocketProcessor
SocketProcessor没啥好说的,它主要就是判断了一下请求的连接的条件是否都已满足,若满足之后就通过调用getHandler().process()方法把该请求交由Processor处理了。Processor和SocketProcessor不是同一个东西,SocketProcessor是NIOEndpoint的一个内部类,Processor是和NIOEndpoint同一等级的一个类,主要负责把字节流解析成Tomcat的Request对象。 -
Executor
Executor 是 Tomcat 定制版的线程池,扩展了Java原生的线程池.
后面会有单独的章节来解析该线程池