Tomcat NIO(13)-BlockPoller

上一篇文章里我们主要介绍了 tomcat NIO 中响应数据的写入,在这里我们主要介绍 BlockPoller 线程。

tomcat NIO 架构中会有一个 BlockPoller 线程,该线程主要处理阻塞的读写操作。对于请求体数据读取,根据以前文章一般由 tomcat io 线程进行,当数据不可读的时候的时候(客户端数据未发送完毕),会注册封装的 OPEN_READ 事件到 BlockPoller 线程中,然后阻塞当前线程(一般为tomcat io线程)。对于响应数据的写入,根据以前文章一般也由 tomcat io 线程进行,当数据不可写的时候(原始 socket 发送缓冲区满),会注册封装的 OPEN_WRITE 事件对象到 BlockPoller 线程中,然后阻塞当前线程。

BlockPoller 实例都有一个 NIO selector 对象,主要用于监测注册在原始 scoket 上的事件是否发生。该实例有事件队列 SynchronizedQueue<PollerEvent>,用来存放发生的事件,一般该事件队列的元素由前一篇文章介绍的 tomcat io 线程放入(当请求体不可读或者响应数据不可写的时候)。对于该 block poller thread ,主要包含如下 :

  • 启动 block poller 线程

  • 添加事件到事件队列

  • 对原始 socket 注册事件

  • block poller 线程核心逻辑

  • block poller 线程的等待与唤醒

启动BlockPoller线程

该线程的启动主要在以前文章中介绍的架构类 NioBlockingSelector 的 open() 方法中完成,相关核心源代码如下:

public void open(String name, Selector selector) {
    sharedSelector = selector;
    poller = new BlockPoller();
    poller.selector = sharedSelector;
    poller.setDaemon(true);
    poller.setName(name + "-BlockPoller");
    poller.start();
}
  • 该代码基于 tomcat v 9.0.21,其中 block poller thread 数目始终为 1。

  • 该代码启动 block poller thread ,并设置其为后台线程。

添加事件到事件队列

该工作主要由 BlockPoller 类的 add() 方法完成,核心源码如下:

protected Selector selector = null;
protected final SynchronizedQueue<Runnable> events = new SynchronizedQueue<>();
protected final AtomicInteger wakeupCounter = new AtomicInteger(0);


public void add(final NioSocketWrapper key, final int ops, final KeyReference ref) {
        if (key == null) {
            return;
        }
        NioChannel nch = key.getSocket();
        final SocketChannel ch = nch.getIOChannel();
        if (ch == null) {
            return;
        }
        Runnable r = new RunnableAdd(ch, key, ops, ref);
        events.offer(r);
        wakeup();
}
public void wakeup() {
    if (wakeupCounter.addAndGet(1)==0) {
        selector.wakeup();
    }
 }
  • 该实例有SynchronizedQueue<PollerEvent> 队列,主要用来存放发生的事件,事件类型为 RunnableAdd 。

  • add() 方法一般由以前文章介绍的 tomcat io 线程调用(当请求体不可读或者响应数据不可写的时候)。

  • add() 方法直接调用队列对象 offer() 方法,将元素加入队列。同时根据 wakeupCounter 增加之后的值是否为 0 来决定是否唤醒 selector (详细会在 poller 线程的等待与唤醒部分讲解)。

对原始socket注册事件

该工作主要由 BlockPoller 类的 events() 方法和队列元素 RunnableAdd 类的 run() 方法完成,核心源码如下:

//BlockPoller
public boolean events() {
      Runnable r = null;
      int size = events.size();
      for (int i = 0; i < size && (r = events.poll()) != null; i++) {
          r.run();
      }
      return (size > 0);
 }


//RunnableAdd
public void run() {
      SelectionKey sk = ch.keyFor(selector);
      try {
              if (sk == null) {
                  sk = ch.register(selector, ops, key);
                  ref.key = sk;
              } else if (!sk.isValid()) {
                  cancel(sk, key, ops);
              } else {
                  sk.interestOps(sk.interestOps() | ops);
              }
          } catch (CancelledKeyException cx) {
              cancel(sk, key, ops);
          } catch (ClosedChannelException cx) {
              cancel(null, key, ops);
          }
 }
  • BlockPoller 的 event() 方法会遍历实例中事件队列中的所有 RunnableAdd 事件对象,然后依次调用该对象的 run() 方法。

  • RunnableAdd 的 run() 方法会把原始 socket 感兴趣的事件注册到 selector 对象中。

BlockPoller线程核心逻辑

该工作主要由 BlockPoller 对象实例的 run() 方法和 countDown() 方法完成,核心源码如下:

public void run() {
    while (run) {
        try {
            events();
            int keyCount = 0;
            try {
                int i = wakeupCounter.get();
                if (i > 0) {
                    keyCount = selector.selectNow();
                } else {
                    wakeupCounter.set(-1);
                    keyCount = selector.select(1000);
                }
                wakeupCounter.set(0);
                if (!run) {
                    break;
                }
            } catch (NullPointerException x) {
                // sun bug 5076772 on windows JDK 1.5
                if (selector == null) {
                    throw x;
                }
                if (log.isDebugEnabled()) {
                    log.debug("Possibly encountered sun bug 5076772 on windows JDK 1.5", x);
                }
                continue;
            } catch (CancelledKeyException x) {
                // sun bug 5076772 on windows JDK 1.5
                if (log.isDebugEnabled()) {
                    log.debug("Possibly encountered sun bug 5076772 on windows JDK 1.5", x);
                }
                continue;
            } catch (Throwable x) {
                ExceptionUtils.handleThrowable(x);
                log.error(sm.getString("nioBlockingSelector.selectError"), x);
                continue;
            }
            Iterator<SelectionKey> iterator = keyCount > 0 ? selector.selectedKeys().iterator() : null;
            while (run && iterator != null && iterator.hasNext()) {
                SelectionKey sk = iterator.next();
                NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();
                try {
                    iterator.remove();
                    sk.interestOps(sk.interestOps() & (~sk.readyOps()));
                    if (sk.isReadable()) {
                        countDown(socketWrapper.getReadLatch());
                    }
                    if (sk.isWritable()) {
                        countDown(socketWrapper.getWriteLatch());
                    }
                } catch (CancelledKeyException ckx) {
                    sk.cancel();
                    countDown(socketWrapper.getReadLatch());
                    countDown(socketWrapper.getWriteLatch());
                }
            }
        } catch (Throwable t) {
            log.error(sm.getString("nioBlockingSelector.processingError"), t);
        }
    }
    events.clear();
    if (selector.isOpen()) {
        try {
            selector.selectNow();
        } catch (Exception ignore) {
            if (log.isDebugEnabled())
                log.debug("", ignore);
        }
    }
    try {
        selector.close();
    } catch (Exception ignore) {
        if (log.isDebugEnabled())
            log.debug("", ignore);
    }
}


public void countDown(CountDownLatch latch) {
    if (latch == null) {
        return;
    }
    latch.countDown();
}
  • run() 方法是一个循环来处理整个 block poller 线程的逻辑。

  • 如果线程循环逻辑结束会把事件队列清空,同时也把 selector 关闭。

  • run() 方法会先调用前面介绍的 event() 方法来对队列中所有原始 socket 注册事件。

  • 然后调用 NIO API 中的 selector.selectNow() 和 selector.select() 方法来监听是否有 event() 方法注册的事件发生(这里会涉及到 BlockPoller 线程的阻塞与唤醒这种比较优雅的设计,详细在后面文章单独讲解)。

  • 如果 selector 有检测到事件发生,并且原始 socket 可读或者可写,则对调用 countDown() 方法。

  • 在countDown() 方法里,调用和以前文章介绍的 NioSocketWrapper 对象实例关联的 readLatch 或者 writeLatch 的 countDown() 方法。

  • 根据 tomcat 请求数据读取和响应数据写入相关文章的介绍,数据不可读或者不可写的时候,会调用 readLatch 或者 writeLatch 的 await() 方法来阻塞当前线程(一般阻塞 tomcat io线程)。当数据可读或可写的时候,调用 readLatch 或者 writeLatch 的 countDown() 方法,由 poller thread 唤醒阻塞的线程(一般为 tomcat io线程),继续处理数据的读写。

目前先写到这里,下一篇文章里我们继续介绍 tomcat NIO 中的 block poller 线程的阻塞与唤醒。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值