尽管通道准备就绪,但Java NIO Selector select()返回0

时间:2012-03-30 09:05:45

标签: java nio

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

  1. 注册频道准备就绪
  2. wakeup()'ed
  3. 线程被中断
  4. 由此,我对select()返回0的情况做了一些假设:

    • 一定是2.或3。
    • selectedKeys()应返回空ResultSet
    • 我无需致电selectedKeys(),可以继续下一次循环迭代,再次调用select()

    但是,我遇到了select()返回0的情况,尽管有一个就绪通道。 selectedKeys()按预期返回Set,其中SelectionKeyselect()

    即使对SelectionKey的多次调用也始终返回0,直到处理完频道并移除了select()。这种情况基本上以无限循环结束,因为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); } } 没有阻止但总是立即返回0。

    简化代码:

    select()

    为什么 for (SelectionKey key : readyKeys) { if (key.isValid() && key.isReadable()) { // Take action... } readyKeys.remove(key); } 会返回0,尽管有一个就绪通道? 建议的处理方法是什么?

    修改

    更改此内容:

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

    到这个

    continue

    解决了这个问题。在某些情况下,代码会在for密钥之前remove() Iterator<SelectionKey> iterator; SelectionKey key; while (true) { // ... iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()) { key = iterator.next(); iterator.remove(); // ... } } 循环。

    编辑2:

    我刚刚了解到我的 foreach 循环选定的键集是不好的。 foreach 使用set的迭代器。在迭代时直接修改集合(不是通过迭代器的方法)可能会导致“任意的,不确定的”行为。

    选定的密钥集可能会提供故障快速迭代器。 失败迭代器检测此类修改并在下一次迭代时抛出ConcurrentModificationException。因此,修改 foreach 中的集合可能会导致不确定行为,或者可能导致异常 - 具体取决于迭代器实现。

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

    {{1}}

2 个答案:

答案 0 :(得分:8)

select()会返回更改的密钥数。因此,如果密钥在select()调用之前已经就绪,那么它可能会返回0但selectedKeys可能是非空的。

答案 1 :(得分:2)

正如您所说,这是因为您没有从所选集中删除所选的密钥。

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

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

这样,您可以使用任何类型的循环(&#34;常规&#34; for或迭代器)并确保删除所有就绪密钥,无论您在{ {1}}(包括有问题的for)。根据{{​​1}}在内部执行的操作,调用continue一次而不是多次iterator.remove()可能更有效(尽管这可能是微优化)。