我的Java NIO选择器是使用select()实现的,所以它会阻塞,直到出现以下任何一个:
由此,我对select()
返回0的情况做了一些假设:
ResultSet
selectedKeys()
,可以继续下一次循环迭代,再次调用select()
但是,我遇到了select()
返回0的情况,尽管有一个就绪通道。 selectedKeys()
按预期返回Set
,其中SelectionKey
为select()
。
即使对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}}
答案 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()
可能更有效(尽管这可能是微优化)。