【问题标题】:Java NIO Selector select() returns 0 although channels are readyJava NIO Selector select() 返回 0 尽管通道已准备好
【发布时间】:2012-03-30 09:05:44
【问题描述】:

我的 Java NIO 选择器是使用 select() 实现的,因此它会一直阻塞,直到出现以下任何一种情况:

  1. 已注册的频道已准备就绪
  2. wakeup()'ed
  3. 线程被中断

由此,我对select()返回0的情况做了一些假设:

  • 一定是原因 2. 或 3.
  • selectedKeys() 应该返回一个空的 ResultSet
  • 我不需要调用selectedKeys(),可以继续进行下一次循环迭代,此时select() 将再次被调用

然而,我遇到了select()返回0的情况,尽管有一个准备好的频道。 selectedKeys() 按预期返回一个 Set 和 1 SelectionKey

即使对select() 的多次调用也总是返回0,直到通道被处理并且SelectionKey 被删除。这种情况基本上以无限循环结束,因为select() 不会阻塞,而是总是立即返回 0。

简化代码:

Selector selector = Selector.open();

SocketChannel channel;

for (...) { // for each node
  // Create and connect channels...
  ...

  channel.configureBlocking(false);
  channel.register(selector, SelectionKey.OP_READ, someRelatedObject);
}

int ready;
Set<SelectionKey> readyKeys;
while (true) {
  ready = selector.select();
  readyKeys = selector.selectedKeys();

  System.out.println("Ready channels: " + ready);
  System.out.println("Selected channels: " + readyKeys.size());

  if (ready == 0) {
    continue;
  }

  for (SelectionKey key : readyKeys) {
    if (key.isValid() && key.isReadable()) {
      // Take action...
    }

    readyKeys.remove(key);
  }
}

为什么select()虽然有一个准备好的频道却返回0? 建议的处理方法是什么?

编辑:

改变这个:

  for (SelectionKey key : readyKeys) {
    if (key.isValid() && key.isReadable()) {
      // Take action...
    }

    readyKeys.remove(key);
  }

到这里

  for (SelectionKey key : readyKeys) {
    readyKeys.remove(key);

    if (key.isValid() && key.isReadable()) {
      // Take action...
    }
  }

解决了这个问题。在某些情况下,代码会在 remove() 密钥之前 continue for 循环。

编辑 2:

我最近才知道我的 foreach 循环在选定的键集上很糟糕。 foreach 使用集合的迭代器。在迭代集合时直接修改集合(而不是通过迭代器的方法)可能会导致“任意的、不确定的”行为。

选定的键集可以提供 fail-fast 迭代器。 Fail-fast 迭代器检测到此类修改并在下一次迭代时抛出ConcurrentModificationException。因此,修改 foreach 中的集合可能会导致行为不确定或导致异常 - 取决于迭代器的实现。

解决方案:不要使用 foreach。使用迭代器并通过iterator.remove() 删除密钥。

Iterator<SelectionKey> iterator;
SelectionKey key;
while (true) {
  // ...

  iterator = selector.selectedKeys().iterator();
  while (iterator.hasNext()) {
    key = iterator.next();
    iterator.remove();
    // ...
  }
}

【问题讨论】:

    标签: java nio


    【解决方案1】:

    select() 返回已更改的键的数量因此,如果在调用 select() 之前已准备好键,则它可以返回 0,但 selectedKeys 可以是非空的。

    【讨论】:

      【解决方案2】:

      正如您所说,这是因为您没有从选定的 Set 中删除选定的键。

      由于我们通常希望在处理完 Set 后删除 所有 选定的键,因此您可以在循环之后对该 Set 调用 clear(),这可以是 foreach 或您想要的任何类型的循环:

      Set<SelectionKey> readyKeys = selector.selectedKeys();
      for (SelectionKey k : readyKeys) {
          // Process k
      }
      readyKeys.clear();
      

      这样,您可以使用任何类型的循环(“常规”for 或迭代器)并确保删除所有就绪键,无论您在 for 内做什么(包括有问题的 @ 987654325@)。根据iterator.remove() 在内部所做的事情,调用clear() 一次而不是多次调用iterator.remove() 可能更有效(尽管这可能是微优化)。

      【讨论】:

      • 没有回答这个问题:“为什么select()虽然有一个准备好的频道却返回0?”
      • @EJP 你是对的,我已经更正了。这足以消除您的反对票吗?
      • 与通过iterator.remove() 立即删除每个SelectionKey 相比有什么优势?
      • @riha:选择您认为更合适的循环类型,并确保您不会忘记任何键。除非您想对 when / if 进行精细控制,否则您希望删除密钥。我编辑了我的答案以包含它。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-14
      • 1970-01-01
      • 2018-02-04
      • 1970-01-01
      • 2021-08-02
      • 1970-01-01
      相关资源
      最近更新 更多