通过Tomcat的Http11NioProtocol源码学习Java NIO设计(2)

Poller线程会从Poller.events队列中取出PollerEvent对象,并运行PollerEvent.run()方法。在PollerEvent.run()方法中发现是OP_REGISTER事件,则会在Poller.selector上注册SocketChannel对象的OP_READ就绪事件。

代码3:PollerEvent.run()方法代码片段

public void run() {
  if ( interestOps == OP_REGISTER ) {
      try {
          //在Poller.selector上注册OP_READ就绪事件
          socket.getIOChannel().register(socket .getPoller().getSelector(), SelectionKey.OP_READ , key );
      } catch (Exception x) {
          log.error("" , x);
      }
  }
  ......
}


至此,一个客户端连接准备工作就已经完成了。我们获得了一个客户端的SocketChannel, 并注册OP_READ就绪事件到Poller.selector上(如图1)。下面就可以进行数据读写了。

图1:ServerSocketChannel和SocketChannel的初始化状态

init

2. Poller.selector的wakeup方法
Poller线程会做如下工作:

1) 通过selection操作获取已经选中的SelectionKey数量;

2) 执行Poller.events队列中的PollerEvent;

3) 处理已经选中的SelectionKey。

当有新PollerEvent对象加入Poller.events队列中,需要尽快执行第二步,而不应该阻塞的selection操作中。所以就需要配合Selector.wakeup()方法来实现这个需求。Tomcat使用信号量wakeupCounter来控制Selector.wakeup()方法,阻塞Selector.select()方法和非阻塞Selector.selectNow()方法的使用。

当有新PollerEvent对象加入Poller.events队列中,会按照如下条件执行Selector.wakeup()方法。

当wakeupCounter加1后等于0,说明Poller.selector阻塞在selection操作,这时才需要调用Selector.wakeup()方法。
当wakeupCounter加1后不等于0,说明Poller.selector没有阻塞在selection操作,则不需要调用Selector.wakeup()方法。并且为了尽快执行第二步,Poller线程在下一次直接调用非阻塞方法Selector.selectNow()。
代码4:Poller.addEvent()方法,实现将PollerEvent对象加入Poller.events队列中。

public void addEvent(Runnable event) {
  events.offer(event);
  //如果wakeupCount加1后等于0,则调用wakeup方法
  if ( wakeupCounter .incrementAndGet() == 0 ) selector.wakeup();
}


代码5: Poller线程的selection操作代码
if (wakeupCounter .get()>0) {
  keyCount = selector.selectNow();
 else {
  wakeupCounter.set(-1);
  keyCount = selector.select(selectorTimeout );
}
wakeupCounter.set(0);


这样的设计因为Java NIO的wakeup有如下的特性:
在Selector对象上调用wakeup()方法将会导致第一个没有返回的selection操作立即返回。如果当前没有进行的selection操作,那么下一次的select()方法的调用将立即返回。而这个将wakeup行为延迟到下一个select()方法经常不是我们想要的(当然也不是Tomcat想要的)。我们一般只是想从sleeping的线程wakeup,但允许接下来的selection操作正常处理。
所以,Tomcat通过wakeupCounter信号量的变化来控制只有阻塞在selection操作的时候才调用Selector.wakeup()方法。当有新PollerEvent对象加入Poller.events队列中,并且没有处于阻塞在selection操作中,则直接调用非阻塞方法Selector.selectNow()。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/1dab7c19012d33e47fea6c5ad2a837e0.html