既然丢到队列了,那我们看看什么时候去队列取的呢?
@Override public void run() { // Loop until destroy() is called while (true) { boolean hasEvents = false; // 检查events hasEvents = events(); } }这里我们跟一下events()。
public boolean events() { boolean result = false; PollerEvent pe = null; for (int i = 0, size = events.size(); i < size && (pe = events.poll()) != null; i++ ) { result = true; pe.run(); ... } return result; }这里的
pe = events.poll()就是去队列拉取事件,拉取到了之后,就会赋值给pe,然后下面就调用了pe.run方法。
pe的类型是PollerEvent,我们看看其run方法会干啥?
@Override public void run() { if (interestOps == OP_REGISTER) { try { socket.getIOChannel().register(socket.getSocketWrapper().getPoller().getSelector(), SelectionKey.OP_READ, socket.getSocketWrapper()); } catch (Exception x) { log.error(sm.getString("endpoint.nio.registerFail"), x); } } }这个方法难理解吗,看着有点吓人,其实就是把这个新的连接,向selector注册,感兴趣的io事件为OP_READ。后续呢,这个连接的io读写,就全由本poller的selector包了。
tomcat如何处理客户端读事件我们说了,poller是个线程,在其runnable实现里,除了要处理上面的新连接注册到selector这个事,还要负责io读写,这部分逻辑就是在:
Iterator<SelectionKey> iterator=selector.selectedKeys().iterator(); while (iterator != null && iterator.hasNext()) { SelectionKey sk = iterator.next(); NioSocketWrapper socketWrapper = sk.attachment(); processKey(sk, socketWrapper); }最后一行的processKey,会调用如下逻辑,将工作甩锅给http-nio-8080-exec-2@5076这类打杂的线程。
public boolean processSocket(SocketWrapperBase<S> socketWrapper,SocketEvent event, boolean dispatch) { Executor executor = getExecutor(); executor.execute(sc); return true; }给个图的话,大概就是如下的红线流程部分了:
小结好了,到了课后思考时间了,我们也说了,最终会交给http-nio-8080-exec-2@5076这类线程所在的线程池,那假设这些线程全都在sleep,会发生什么呢?